From ffe6c537d0ba6a254e676155caaffcfcc5fc71c9 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Fri, 24 Apr 2026 19:28:21 +0000 Subject: [PATCH] feat(azure): add infra + build/deploy scripts for Azure Container Apps Split Bicep into infra.bicep (one-time: ACR + Log Analytics + Env) and container-app.bicep (per-deploy: Container App with ACR auth). Add 1-infra.sh and 2-build-deploy.sh shell scripts with config.sh.example covering all .env variables. Gitignore azure/config.sh to prevent secret leakage. Co-Authored-By: Claude Sonnet 4.6 --- .gitignore | 3 + azure/1-infra.sh | 98 +++++++++++++++++++++++++ azure/2-build-deploy.sh | 147 ++++++++++++++++++++++++++++++++++++++ azure/config.sh.example | 51 +++++++++++++ azure/container-app.bicep | 106 +++++++++++---------------- azure/infra.bicep | 75 +++++++++++++++++++ 6 files changed, 416 insertions(+), 64 deletions(-) create mode 100755 azure/1-infra.sh create mode 100755 azure/2-build-deploy.sh create mode 100644 azure/config.sh.example create mode 100644 azure/infra.bicep diff --git a/.gitignore b/.gitignore index 53ad9862..1b8926ef 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,6 @@ backend/uploads/ data/ # Git worktrees .worktrees/ + +# Configuració Azure amb secrets (no comitejar mai) +azure/config.sh diff --git a/azure/1-infra.sh b/azure/1-infra.sh new file mode 100755 index 00000000..3bfdb4aa --- /dev/null +++ b/azure/1-infra.sh @@ -0,0 +1,98 @@ +#!/usr/bin/env bash +# ───────────────────────────────────────────────────────────────────────────── +# 1-infra.sh — Crea la infraestructura base de MiroFish a Azure +# +# Executa UNA SOLA VEGADA (o si vols recrear la infraestructura). +# Idempotent: pot executar-se múltiples vegades sense errors. +# +# Prerequisites: +# - az login executat +# - azure/config.sh existent (còpia de config.sh.example) +# +# Crea: +# - Resource Group: rg_mirofish +# - Azure Container Registry (ACR): ${PROJECT_NAME}acr +# - Log Analytics Workspace: ${PROJECT_NAME}-logs +# - Container Apps Environment: ${PROJECT_NAME}-env +# ───────────────────────────────────────────────────────────────────────────── +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# ── Carregar configuració ───────────────────────────────────────────────────── +CONFIG_FILE="${SCRIPT_DIR}/config.sh" +if [[ ! -f "$CONFIG_FILE" ]]; then + echo "ERROR: No s'ha trobat azure/config.sh" + echo " Còpia l'exemple: cp azure/config.sh.example azure/config.sh" + echo " Després omple els valors i torna a executar." + exit 1 +fi +# shellcheck source=config.sh.example +source "$CONFIG_FILE" + +# ── Validar variables obligatòries ─────────────────────────────────────────── +REQUIRED_VARS=( + AZURE_SUBSCRIPTION_ID AZURE_LOCATION + RESOURCE_GROUP PROJECT_NAME +) +for var in "${REQUIRED_VARS[@]}"; do + if [[ -z "${!var:-}" ]]; then + echo "ERROR: La variable $var no està configurada a config.sh" + exit 1 + fi +done + +ACR_NAME="${PROJECT_NAME}acr" + +echo "════════════════════════════════════════════════════════" +echo " MiroFish — Creació d'infraestructura Azure" +echo "════════════════════════════════════════════════════════" +echo " Subscripció : $AZURE_SUBSCRIPTION_ID" +echo " Localització: $AZURE_LOCATION" +echo " Grup recurs : $RESOURCE_GROUP" +echo " ACR : $ACR_NAME" +echo "════════════════════════════════════════════════════════" +echo "" + +# ── Seleccionar subscripció ─────────────────────────────────────────────────── +echo "→ Seleccionant subscripció..." +az account set --subscription "$AZURE_SUBSCRIPTION_ID" + +# ── Registrar proveïdors necessaris ────────────────────────────────────────── +echo "→ Registrant proveïdors Azure (pot trigar uns minuts la primera vegada)..." +az provider register --namespace Microsoft.App --wait +az provider register --namespace Microsoft.OperationalInsights --wait +az provider register --namespace Microsoft.ContainerRegistry --wait + +# ── Crear Resource Group ────────────────────────────────────────────────────── +echo "→ Creant Resource Group '$RESOURCE_GROUP'..." +az group create \ + --name "$RESOURCE_GROUP" \ + --location "$AZURE_LOCATION" \ + --output none +echo " ✓ Resource Group llest" + +# ── Desplegar infraestructura via Bicep ────────────────────────────────────── +echo "→ Desplegant infraestructura (ACR + Log Analytics + Container Apps Env)..." +INFRA_OUTPUT=$(az deployment group create \ + --resource-group "$RESOURCE_GROUP" \ + --template-file "${SCRIPT_DIR}/infra.bicep" \ + --parameters \ + projectName="$PROJECT_NAME" \ + location="$AZURE_LOCATION" \ + --output json) + +# Extreure outputs del desplegament +ACR_LOGIN_SERVER=$(echo "$INFRA_OUTPUT" | python3 -c "import sys,json; print(json.load(sys.stdin)['properties']['outputs']['acrLoginServer']['value'])") +ACR_NAME_OUT=$(echo "$INFRA_OUTPUT" | python3 -c "import sys,json; print(json.load(sys.stdin)['properties']['outputs']['acrName']['value'])") +ENV_ID=$(echo "$INFRA_OUTPUT" | python3 -c "import sys,json; print(json.load(sys.stdin)['properties']['outputs']['containerAppsEnvId']['value'])") + +echo "" +echo "════════════════════════════════════════════════════════" +echo " Infraestructura creada correctament!" +echo "════════════════════════════════════════════════════════" +echo " ACR Login Server : $ACR_LOGIN_SERVER" +echo " Container Apps Env ID: $ENV_ID" +echo "" +echo " Proper pas: bash azure/2-build-deploy.sh" +echo "════════════════════════════════════════════════════════" diff --git a/azure/2-build-deploy.sh b/azure/2-build-deploy.sh new file mode 100755 index 00000000..b52cedb3 --- /dev/null +++ b/azure/2-build-deploy.sh @@ -0,0 +1,147 @@ +#!/usr/bin/env bash +# ───────────────────────────────────────────────────────────────────────────── +# 2-build-deploy.sh — Build Docker + push a ACR + deploy Container App +# +# Executar a cada nova versió de l'aplicació. +# Requereix que 1-infra.sh hagi estat executat prèviament. +# +# Prerequisites: +# - az login executat +# - azure/config.sh existent i configurat +# - Docker instal·lat i en execució +# - Infraestructura creada (azure/1-infra.sh) +# ───────────────────────────────────────────────────────────────────────────── +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" + +# ── Carregar configuració ───────────────────────────────────────────────────── +CONFIG_FILE="${SCRIPT_DIR}/config.sh" +if [[ ! -f "$CONFIG_FILE" ]]; then + echo "ERROR: No s'ha trobat azure/config.sh" + echo " Còpia l'exemple: cp azure/config.sh.example azure/config.sh" + exit 1 +fi +# shellcheck source=config.sh.example +source "$CONFIG_FILE" + +# ── Validar variables obligatòries ─────────────────────────────────────────── +REQUIRED_VARS=( + AZURE_SUBSCRIPTION_ID RESOURCE_GROUP PROJECT_NAME + DEMO_PASSWORD SECRET_KEY LLM_API_KEY LLM_BASE_URL LLM_MODEL_NAME ZEP_API_KEY +) +for var in "${REQUIRED_VARS[@]}"; do + if [[ -z "${!var:-}" ]]; then + echo "ERROR: La variable $var no està configurada a config.sh" + exit 1 + fi +done + +ACR_NAME="${PROJECT_NAME}acr" + +# ── Seleccionar subscripció ─────────────────────────────────────────────────── +echo "→ Seleccionant subscripció..." +az account set --subscription "$AZURE_SUBSCRIPTION_ID" + +# ── Obtenir dades de la infraestructura existent ────────────────────────────── +echo "→ Obtenint dades de la infraestructura..." + +ACR_LOGIN_SERVER=$(az acr show \ + --name "$ACR_NAME" \ + --resource-group "$RESOURCE_GROUP" \ + --query loginServer --output tsv) + +ENV_ID=$(az containerapp env show \ + --name "${PROJECT_NAME}-env" \ + --resource-group "$RESOURCE_GROUP" \ + --query id --output tsv) + +if [[ -z "$ACR_LOGIN_SERVER" || -z "$ENV_ID" ]]; then + echo "ERROR: No s'ha trobat la infraestructura. Executa primer: bash azure/1-infra.sh" + exit 1 +fi + +# ── Generar tag de versió ───────────────────────────────────────────────────── +# Format: - per a traçabilitat +GIT_SHA=$(git -C "$REPO_ROOT" rev-parse --short HEAD 2>/dev/null || echo "nogit") +TIMESTAMP=$(date +%Y%m%d%H%M) +IMAGE_TAG="${GIT_SHA}-${TIMESTAMP}" +FULL_IMAGE="${ACR_LOGIN_SERVER}/${PROJECT_NAME}:${IMAGE_TAG}" +LATEST_IMAGE="${ACR_LOGIN_SERVER}/${PROJECT_NAME}:latest" + +echo "" +echo "════════════════════════════════════════════════════════" +echo " MiroFish — Build & Deploy" +echo "════════════════════════════════════════════════════════" +echo " ACR : $ACR_LOGIN_SERVER" +echo " Imatge : ${PROJECT_NAME}:${IMAGE_TAG}" +echo " Container Env : ${PROJECT_NAME}-env" +echo "════════════════════════════════════════════════════════" +echo "" + +# ── Login a l'ACR ───────────────────────────────────────────────────────────── +echo "→ Login a l'ACR..." +az acr login --name "$ACR_NAME" + +# ── Build de la imatge Docker ───────────────────────────────────────────────── +echo "→ Build de la imatge Docker..." +docker build \ + --tag "$FULL_IMAGE" \ + --tag "$LATEST_IMAGE" \ + "$REPO_ROOT" +echo " ✓ Build completat" + +# ── Push de la imatge a l'ACR ───────────────────────────────────────────────── +echo "→ Push a l'ACR ($FULL_IMAGE)..." +docker push "$FULL_IMAGE" +docker push "$LATEST_IMAGE" +echo " ✓ Push completat" + +# ── Obtenir credencials ACR per al Bicep ───────────────────────────────────── +echo "→ Obtenint credencials ACR..." +ACR_USERNAME=$(az acr credential show \ + --name "$ACR_NAME" \ + --resource-group "$RESOURCE_GROUP" \ + --query username --output tsv) +ACR_PASSWORD=$(az acr credential show \ + --name "$ACR_NAME" \ + --resource-group "$RESOURCE_GROUP" \ + --query "passwords[0].value" --output tsv) + +# ── Desplegar Container App via Bicep ───────────────────────────────────────── +echo "→ Desplegant Container App..." +DEPLOY_OUTPUT=$(az deployment group create \ + --resource-group "$RESOURCE_GROUP" \ + --template-file "${SCRIPT_DIR}/container-app.bicep" \ + --parameters \ + projectName="$PROJECT_NAME" \ + containerAppsEnvId="$ENV_ID" \ + containerImage="$FULL_IMAGE" \ + acrLoginServer="$ACR_LOGIN_SERVER" \ + acrUsername="$ACR_USERNAME" \ + acrPassword="$ACR_PASSWORD" \ + demoPassword="$DEMO_PASSWORD" \ + llmApiKey="$LLM_API_KEY" \ + llmBoostApiKey="${LLM_BOOST_API_KEY:-}" \ + zepApiKey="$ZEP_API_KEY" \ + secretKey="$SECRET_KEY" \ + llmBaseUrl="$LLM_BASE_URL" \ + llmModelName="$LLM_MODEL_NAME" \ + llmBoostBaseUrl="${LLM_BOOST_BASE_URL:-}" \ + llmBoostModelName="${LLM_BOOST_MODEL_NAME:-}" \ + oasisDefaultMaxRounds="${OASIS_DEFAULT_MAX_ROUNDS:-10}" \ + reportAgentMaxToolCalls="${REPORT_AGENT_MAX_TOOL_CALLS:-5}" \ + reportAgentMaxReflectionRounds="${REPORT_AGENT_MAX_REFLECTION_ROUNDS:-2}" \ + reportAgentTemperature="${REPORT_AGENT_TEMPERATURE:-0.5}" \ + --output json) + +FQDN=$(echo "$DEPLOY_OUTPUT" | python3 -c "import sys,json; print(json.load(sys.stdin)['properties']['outputs']['containerAppFqdn']['value'])") + +echo "" +echo "════════════════════════════════════════════════════════" +echo " Deploy completat!" +echo "════════════════════════════════════════════════════════" +echo " URL de l'aplicació: https://$FQDN" +echo " Imatge desplegada : $FULL_IMAGE" +echo "════════════════════════════════════════════════════════" diff --git a/azure/config.sh.example b/azure/config.sh.example new file mode 100644 index 00000000..7e98bb09 --- /dev/null +++ b/azure/config.sh.example @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +# ───────────────────────────────────────────────────────────────────────────── +# Configuració de desplegament MiroFish a Azure +# +# INSTRUCCIONS: +# 1. Còpia aquest fitxer a azure/config.sh (NO comitegis config.sh — té secrets) +# cp azure/config.sh.example azure/config.sh +# 2. Omple tots els valors marcats amb <...> +# 3. Executa: az login +# 4. Executa: bash azure/1-infra.sh (una sola vegada) +# 5. Executa: bash azure/2-build-deploy.sh (a cada nova versió) +# ───────────────────────────────────────────────────────────────────────────── + +# ── Subscripció i localització Azure ───────────────────────────────────────── +AZURE_SUBSCRIPTION_ID="" +AZURE_LOCATION="westeurope" # canvia si prefereixes altra regió + +# ── Noms de recursos (pots deixar els valors per defecte) ───────────────────── +RESOURCE_GROUP="rg_mirofish" +PROJECT_NAME="mirofish" # prefix per a tots els recursos Azure +# Nota: el nom de l'ACR serà "${PROJECT_NAME}acr" (sense guions, tot minúscula) + +# ── Secrets de l'aplicació ──────────────────────────────────────────────────── + +# Contrasenya de l'usuari "demo" per fer login a l'app +DEMO_PASSWORD="" + +# Flask SECRET_KEY per signar tokens JWT +# Genera-la amb: python -c "import secrets; print(secrets.token_hex(32))" +SECRET_KEY="" + +# ── LLM principal (OpenAI-compatible) ───────────────────────────────────────── +LLM_API_KEY="" +LLM_BASE_URL="https://dashscope.aliyuncs.com/compatible-mode/v1" +LLM_MODEL_NAME="qwen-plus" + +# ── LLM accelerador (opcional — deixar buit per desactivar) ─────────────────── +LLM_BOOST_API_KEY="" +LLM_BOOST_BASE_URL="" +LLM_BOOST_MODEL_NAME="" + +# ── Zep Cloud (graf de memòria) ─────────────────────────────────────────────── +ZEP_API_KEY="" + +# ── Simulació OASIS (valors per defecte recomanats) ─────────────────────────── +OASIS_DEFAULT_MAX_ROUNDS="10" + +# ── Report Agent (valors per defecte recomanats) ────────────────────────────── +REPORT_AGENT_MAX_TOOL_CALLS="5" +REPORT_AGENT_MAX_REFLECTION_ROUNDS="2" +REPORT_AGENT_TEMPERATURE="0.5" diff --git a/azure/container-app.bicep b/azure/container-app.bicep index c4d2dda2..577b5f00 100644 --- a/azure/container-app.bicep +++ b/azure/container-app.bicep @@ -1,34 +1,42 @@ // ───────────────────────────────────────────────────────────────────────────── -// MiroFish — Azure Container App -// Segueix les directrius CTTI per a desplegament d'aplicacions a Azure -// (DA/TM: Azure Container Apps com a plataforma de desplegament de contenidors) +// MiroFish — Container App (executar a cada deploy) // -// Paràmetres que l'equip d'ops ha de proporcionar en desplegar: -// Secrets (@secure): demoPassword, llmApiKey, llmBoostApiKey, zepApiKey, secretKey -// Valors: containerImage, llmBaseUrl, llmModelName, llmBoostBaseUrl, -// llmBoostModelName, oasisDefaultMaxRounds, -// reportAgentMaxToolCalls, reportAgentMaxReflectionRounds, -// reportAgentTemperature +// Rep com a paràmetres els outputs d'infra.bicep (containerAppsEnvId, +// acrLoginServer) i desplega/actualitza la Container App amb la nova imatge. +// +// Executar amb: azure/2-build-deploy.sh // // Extensions pendents per a l'equip d'operacions: // - DNS: afegir CNAME a *.intranet.gencat.cat (PRE) / *.gencat.cat (PRO) -// - Xarxa: integrar en VNet Hub-Spoke + Private Link per a serveis interns -// (descomentar el bloc vnetConfiguration a l'entorn) -// - TLS: certificat gestionat via Container Apps o Azure Front Door // - ingress.external: canviar a false per a accés exclusiu per intranet +// - TLS: certificat gestionat via Container Apps o Azure Front Door // ───────────────────────────────────────────────────────────────────────────── -@description('Nom base del projecte (es fa servir per als noms dels recursos)') +@description('Nom base del projecte') param projectName string = 'mirofish' @description('Localització Azure dels recursos') param location string = resourceGroup().location -@description('Imatge Docker completa (registry/imatge:tag)') +@description('ID del Container Apps Environment (output d\'infra.bicep)') +param containerAppsEnvId string + +@description('Imatge Docker completa (acrLoginServer/nom:tag)') param containerImage string +@description('Login server de l\'ACR (ex: mirofsihacr.azurecr.io)') +param acrLoginServer string + // ─── Paràmetres secrets (@secure — mai visibles als logs de desplegament) ──── +@description('Nom d\'usuari de l\'ACR (az acr credential show --name --query username)') +@secure() +param acrUsername string + +@description('Contrasenya de l\'ACR (az acr credential show --name --query passwords[0].value)') +@secure() +param acrPassword string + @description('Contrasenya de l\'usuari demo') @secure() param demoPassword string @@ -45,7 +53,7 @@ param llmBoostApiKey string = '' @secure() param zepApiKey string -@description('SECRET_KEY de Flask per a JWT (generar amb: python -c "import secrets; print(secrets.token_hex(32))")') +@description('SECRET_KEY de Flask per a JWT (python -c "import secrets; print(secrets.token_hex(32))")') @secure() param secretKey string @@ -81,54 +89,31 @@ param reportAgentMaxReflectionRounds string = '2' @description('Temperatura del model LLM per al Report Agent') param reportAgentTemperature string = '0.5' -// ─── Log Analytics Workspace ────────────────────────────────────────────────── -// NOR0016-C: retenció mínima de logs de seguretat = 90 dies -resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2022-10-01' = { - name: '${projectName}-logs' - location: location - properties: { - sku: { - name: 'PerGB2018' - } - retentionInDays: 90 - } -} - -// ─── Container Apps Environment ─────────────────────────────────────────────── -resource containerAppsEnv 'Microsoft.App/managedEnvironments@2023-05-01' = { - name: '${projectName}-env' - location: location - properties: { - appLogsConfiguration: { - destination: 'log-analytics' - logAnalyticsConfiguration: { - customerId: logAnalytics.properties.customerId - sharedKey: logAnalytics.listKeys().primarySharedKey - } - } - // TODO (ops): descomentar per integrar en VNet Hub-Spoke CTTI - // vnetConfiguration: { - // infrastructureSubnetId: '/subscriptions/.../subnets/container-apps-subnet' - // internal: true // true = accés únicament per intranet - // } - } -} - // ─── Container App ───────────────────────────────────────────────────────────── resource containerApp 'Microsoft.App/containerApps@2023-05-01' = { name: projectName location: location properties: { - managedEnvironmentId: containerAppsEnv.id + managedEnvironmentId: containerAppsEnvId configuration: { - // Secrets de Container Apps — mai en text pla a les variables d'entorn + // Secrets: credencials ACR + variables sensibles de l'aplicació secrets: [ - { name: 'demo-password', value: demoPassword } - { name: 'llm-api-key', value: llmApiKey } - { name: 'llm-boost-api-key', value: llmBoostApiKey } - { name: 'zep-api-key', value: zepApiKey } - { name: 'secret-key', value: secretKey } + { name: 'acr-password', value: acrPassword } + { name: 'demo-password', value: demoPassword } + { name: 'llm-api-key', value: llmApiKey } + { name: 'llm-boost-api-key', value: llmBoostApiKey } + { name: 'zep-api-key', value: zepApiKey } + { name: 'secret-key', value: secretKey } + ] + + // Credencials del registre privat (ACR) + registries: [ + { + server: acrLoginServer + username: acrUsername + passwordSecretRef: 'acr-password' + } ] // Ingrés: port únic 5001 (Flask serveix frontend + API) @@ -138,11 +123,7 @@ resource containerApp 'Microsoft.App/containerApps@2023-05-01' = { transport: 'http' // TODO (ops): afegir domini corporatiu quan estigui assignat // customDomains: [ - // { - // name: 'mirofish.intranet.gencat.cat' // PRE - // certificateId: '' - // bindingType: 'SniEnabled' - // } + // { name: 'mirofish.intranet.gencat.cat', certificateId: '...', bindingType: 'SniEnabled' } // ] } @@ -210,11 +191,8 @@ resource containerApp 'Microsoft.App/containerApps@2023-05-01' = { } // ─── Outputs ────────────────────────────────────────────────────────────────── -@description('FQDN de l\'aplicació desplegada') +@description('FQDN públic de l\'aplicació') output containerAppFqdn string = containerApp.properties.configuration.ingress.fqdn @description('Nom del recurs Container App') output containerAppName string = containerApp.name - -@description('ID del workspace de Log Analytics') -output logAnalyticsWorkspaceId string = logAnalytics.id diff --git a/azure/infra.bicep b/azure/infra.bicep new file mode 100644 index 00000000..51cf1968 --- /dev/null +++ b/azure/infra.bicep @@ -0,0 +1,75 @@ +// ───────────────────────────────────────────────────────────────────────────── +// MiroFish — Infraestructura base (executar una sola vegada) +// +// Crea: +// - Azure Container Registry (ACR) per emmagatzemar la imatge Docker +// - Log Analytics Workspace (NOR0016-C: 90 dies retenció) +// - Container Apps Environment (plataforma d'execució CTTI) +// +// Executar amb: azure/1-infra.sh +// ───────────────────────────────────────────────────────────────────────────── + +@description('Nom base del projecte') +param projectName string = 'mirofish' + +@description('Localització Azure dels recursos') +param location string = resourceGroup().location + +// ─── Azure Container Registry ───────────────────────────────────────────────── +// SKU Basic: suficient per a imatges privades sense geo-replicació +resource acr 'Microsoft.ContainerRegistry/registries@2023-01-01-preview' = { + name: '${projectName}acr' // ACR no admet guions, tot minúscula + location: location + sku: { + name: 'Basic' + } + properties: { + adminUserEnabled: true // necessari per a la autenticació des dels scripts + } +} + +// ─── Log Analytics Workspace ────────────────────────────────────────────────── +// NOR0016-C: retenció mínima de logs de seguretat = 90 dies +resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2022-10-01' = { + name: '${projectName}-logs' + location: location + properties: { + sku: { + name: 'PerGB2018' + } + retentionInDays: 90 + } +} + +// ─── Container Apps Environment ─────────────────────────────────────────────── +resource containerAppsEnv 'Microsoft.App/managedEnvironments@2023-05-01' = { + name: '${projectName}-env' + location: location + properties: { + appLogsConfiguration: { + destination: 'log-analytics' + logAnalyticsConfiguration: { + customerId: logAnalytics.properties.customerId + sharedKey: logAnalytics.listKeys().primarySharedKey + } + } + // TODO (ops): descomentar per integrar en VNet Hub-Spoke CTTI + // vnetConfiguration: { + // infrastructureSubnetId: '/subscriptions/.../subnets/container-apps-subnet' + // internal: true + // } + } +} + +// ─── Outputs (usats pels scripts de deploy) ─────────────────────────────────── +@description('URL de login de l\'ACR (ex: mirofsihacr.azurecr.io)') +output acrLoginServer string = acr.properties.loginServer + +@description('Nom del recurs ACR (per a az acr build)') +output acrName string = acr.name + +@description('ID del Container Apps Environment') +output containerAppsEnvId string = containerAppsEnv.id + +@description('ID del Log Analytics Workspace') +output logAnalyticsId string = logAnalytics.id