Cómo desplegar Azure Logic Apps con Terraform

Durante la Semana Santa he estado intentando deshacerme de todas las PoCs que tengo en mi suscripción, incluidas aquellas en las que hago uso de Logic Apps, pero con la intención de levantar el entorno en otro momento que lo necesite. Cuando trabajas con este tipo de recursos y Terraform te darás cuenta de que no es muy intuitivo su despliegue. En este artículo te cuento cómo lo hago yo a día de hoy.

Logic App de ejemplo

Para poder contarte todos los puntos a los que debes prestar atención, voy a utilizar como ejemplo este flujo, que es una variante del artículo donde te hablaba de cómo indexar vídeos con Azure Media Services.

Logic App de ejemplo
Logic App a terraformear

Este hace lo siguiente: cada vez que un nuevo vídeo se almacena en la cuenta de almacenamiento asociada se ejecuta una Azure Function, que a su vez se comunica con Azure Media Services, que analiza el contenido con Video Indexer y devuelve a la Logic App los metadatos obtenidos. Estos son almacenados dentro de una CosmosDB, teniendo como resultado final lo siguiente:

Como ves, bastantes piezas a automatizar, enlazadas entre sí y que nos hace tener que seguir un orden a la hora de desplegar.

Terraform y las Logic Apps

Si has revisado la documentación de Terraform , verás que la forma que está documentada para el despliegue de Logic Apps es definiendo cada una de las acciones que tenemos dentro del flujo.

A mí personalmente me parece demasiado complejo definirlas de esta forma, por lo que he optado por hacerlo de esta otra:

  1. Descargarse las plantillas ARM.
  2. Desplegar Azure Storage, CosmosDB y las API Connections.
  3. Desplegar Azure Function y Azure Media Services.
  4. Desplegar workflow de Logic App enlazando todo lo anterior.

Veamos pues punto por punto.

1. Descargarse las plantillas ARM

Lo habitual cuando quieres automatizar una Logic App es que esta ya la tengas generada a través del diseñador durante la fase de desarrollo. Una vez que tienes definidos todos los pasos, las conexiones necesarias y todo funcionando simplemente debes ir al apartado Automation > Export template y hacer clic en el botón Download para descargarte la plantilla ARM de tu flujo.

Descargar la plantilla ARM de un recurso

Una vez descargada, si abres el archivo, verás en la parte inferior cuántas conexiones estás utilizando para el flujo en cuestión:

También será necesario que te descargues todas las plantillas de las conexiones a las que hace referencia. Por lo que en este ejemplo deberías de acceder a cada una de las API connections dentro del grupo de recursos y descargarte también su plantilla ARM:

API connections de una Azure Logic App
API connections de una Azure Logic App

Ahora que ya tienes todas las plantillas ARM que necesitas, lo ideal es que hagas algunos cambios en ellas, para que sean lo más reutilizables posibles. Hay algunos parámetros que se quedan hardcodeados, como la ubicación de los recursos, el id de tu suscripción, etcétera, que quizás conviene que los parametrices para que puedas utilizarlo donde quieras. Además, el nombre de los parámetros quizás tampoco sean lo más descriptivos del mundo, por lo que puede ser interesante que modifiques sus nombres. En mi flujo ha quedado de la siguiente manera, después de refactorizar la plantilla descargada:

{
    "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "api_connections_location": {
            "type": "String"
        },
        "location": {
            "type": "String"
        },
        "workflow_name": {
            "defaultValue": "video-indexer-flow",
            "type": "String"
        },
        "api_connections_resource_group": {
            "type": "String"
        },
        "storage_resource_group": {
            "type": "String"
        },
        "storage_name": {
            "type": "String"
        },
        "azure_function_resource_group": {
            "type": "String"
        },
        "azure_function_name": {
            "type": "String"
        }
    },
    "variables": {
        "storageAccounts_amsv3store_externalid": "[concat(subscription().id,'/resourceGroups/',parameters('storage_resource_group'),'/providers/Microsoft.Storage/storageAccounts/', parameters('storage_name'))]",
        "sites_amsv3func_externalid": "[concat(subscription().id, '/resourceGroups/', parameters('azure_function_resource_group'),'/providers/Microsoft.Web/sites/',parameters('azure_function_name'))]",
        "connections_azureblob_externalid": "[concat(subscription().id,'/resourceGroups/',parameters('api_connections_resource_group'),'/providers/Microsoft.Web/connections/azureblob')]",
        "connections_azureeventgrid_externalid": "[concat(subscription().id,'/resourceGroups/',parameters('api_connections_resource_group'),'/providers/Microsoft.Web/connections/azureeventgrid')]",
        "connections_cosmosdb_externalid": "[concat(subscription().id,'/resourceGroups/',parameters('api_connections_resource_group'),'/providers/Microsoft.Web/connections/documentdb')]",
        "blobUrl": "[concat('https://',parameters('storage_name'),'.blob.core.windows.net')]"
    },
    "resources": [
        {
            "type": "Microsoft.Logic/workflows",
            "apiVersion": "2017-07-01",
            "name": "[parameters('workflow_name')]",
            "location": "[parameters('location')]",
            "properties": {
                "state": "Enabled",
                "definition": {
                    "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
                    "contentVersion": "1.0.0.0",
                    "parameters": {
                        "$connections": {
                            "defaultValue": {},
                            "type": "Object"
                        }
                    },
                    "triggers": {
                        "When_a_resource_event_occurs": {
                            "splitOn": "@triggerBody()",
                            "type": "ApiConnectionWebhook",
                            "inputs": {
                                "body": {
                                    "properties": {
                                        "destination": {
                                            "endpointType": "webhook",
                                            "properties": {
                                                "endpointUrl": "@{listCallbackUrl()}"
                                            }
                                        },
                                        "filter": {
                                            "includedEventTypes": [
                                                "Microsoft.Storage.BlobCreated"
                                            ],
                                            "subjectBeginsWith": "/blobServices/default/containers/videos/"
                                        },
                                        "topic": "[variables('storageAccounts_amsv3store_externalid')]"

                                    }
                                },
                                "host": {
                                    "connection": {
                                        "name": "@parameters('$connections')['azureeventgrid']['connectionId']"
                                    }
                                },
                                "path": "[concat(subscription().id,'/providers/Microsoft.Storage.StorageAccounts/resource/eventSubscriptions')]",
                                "queries": {
                                    "x-ms-api-version": "2017-06-15-preview"
                                }
                            }
                        }
                    },
                    "actions": {
                        "Create_SAS_URI_by_path": {
                            "runAfter": {
                                "Initialize_variable": [
                                    "Succeeded"
                                ]
                            },
                            "type": "ApiConnection",
                            "inputs": {
                                "body": {
                                    "Permissions": "Read"
                                },
                                "host": {
                                    "connection": {
                                        "name": "@parameters('$connections')['azureblob']['connectionId']"
                                    }
                                },
                                "method": "post",
                                "path": "/datasets/default/CreateSharedLinkByPath",
                                "queries": {
                                    "path": "@{replace(body('Parse_JSON')?['data']?['url'],variables('blobUrl'),'')}"
                                }
                            }
                        },
                        "Create_or_update_document": {
                            "runAfter": {
                                "GetInsights": [
                                    "Succeeded"
                                ]
                            },
                            "type": "ApiConnection",
                            "inputs": {
                                "body": "@body('GetInsights')",
                                "headers": {
                                    "x-ms-documentdb-raw-partitionkey": "\"en-US\""
                                },
                                "host": {
                                    "connection": {
                                        "name": "@parameters('$connections')['cosmosdb']['connectionId']"
                                    }
                                },
                                "method": "post",
                                "path": "/dbs/@{encodeURIComponent('Insights')}/colls/@{encodeURIComponent('videos')}/docs"
                            }
                        },
                        "GetInsights": {
                            "runAfter": {
                                "Create_SAS_URI_by_path": [
                                    "Succeeded"
                                ]
                            },
                            "type": "Function",
                            "inputs": {
                                "body": {
                                    "url": "@body('Create_SAS_URI_by_path')?['WebUrl']"
                                },
                                "function": {
                                    "id": "[concat(variables('sites_amsv3func_externalid'), '/functions/GetInsights')]"

                                }
                            }
                        },
                        "Parse_JSON": {
                            "runAfter": {},
                            "type": "ParseJson",
                            "inputs": {
                                "content": "@triggerBody()",
                                "schema": {
                                    "properties": {
                                        "data": {
                                            "properties": {
                                                "api": {
                                                    "type": "string"
                                                },
                                                "blobType": {
                                                    "type": "string"
                                                },
                                                "clientRequestId": {
                                                    "type": "string"
                                                },
                                                "contentLength": {
                                                    "type": "integer"
                                                },
                                                "contentType": {
                                                    "type": "string"
                                                },
                                                "eTag": {
                                                    "type": "string"
                                                },
                                                "requestId": {
                                                    "type": "string"
                                                },
                                                "sequencer": {
                                                    "type": "string"
                                                },
                                                "storageDiagnostics": {
                                                    "properties": {
                                                        "batchId": {
                                                            "type": "string"
                                                        }
                                                    },
                                                    "type": "object"
                                                },
                                                "url": {
                                                    "type": "string"
                                                }
                                            },
                                            "type": "object"
                                        },
                                        "dataVersion": {
                                            "type": "string"
                                        },
                                        "eventTime": {
                                            "type": "string"
                                        },
                                        "eventType": {
                                            "type": "string"
                                        },
                                        "id": {
                                            "type": "string"
                                        },
                                        "metadataVersion": {
                                            "type": "string"
                                        },
                                        "subject": {
                                            "type": "string"
                                        },
                                        "topic": {
                                            "type": "string"
                                        }
                                    },
                                    "type": "object"
                                }
                            }
                        },
                        "Initialize_variable": {
                            "runAfter": {
                                "Parse_JSON": [
                                    "Succeeded"
                                ]
                            },                            
                            "type": "InitializeVariable",
                            "inputs": {
                                "variables": [
                                    {
                                        "name": "blobUrl",
                                        "type": "string",
                                        "value": "[variables('blobUrl')]"
                                    }
                                ]
                            }
                        }
                    },
                    "outputs": {}
                },
                "parameters": {
                    "$connections": {
                        "value": {
                            "azureblob": {
                                "connectionId": "[variables('connections_azureblob_externalid')]",
                                "connectionName": "azureblob",
                                "id": "[concat(subscription().id,'/providers/Microsoft.Web/locations/',parameters('api_connections_location'),'/managedApis/azureblob')]"

                            },
                            "azureeventgrid": {
                                "connectionId": "[variables('connections_azureeventgrid_externalid')]",
                                "connectionName": "azureeventgrid",
                                "id": "[concat(subscription().id,'/providers/Microsoft.Web/locations/',parameters('api_connections_location'),'/managedApis/azureeventgrid')]"

                            },
                            "cosmosdb": {
                                "connectionId": "[variables('connections_cosmosdb_externalid')]",
                                "connectionName": "cosmosdb",
                                "id": "[concat(subscription().id,'/providers/Microsoft.Web/locations/',parameters('api_connections_location'),'/managedApis/documentdb')]"
                            }
                        }
                    }
                }
            }
        }
    ]
}

Si comparas con la plantilla ARM que te descargas de tu Logic App, en mi ejemplo he procurado en todo momento generalizar la suscripción (usando subscription().id), y los nombres de los recursos, para que pueda reutilizarla en diferentes entornos y con diferentes nombres. También he movido la creación de los id de los diferentes recursos al apartado variables, para que como parámetro solo recoja el nombre del recurso, sin tener que proveer la ruta completa al mismo.

Las conexiónes quedarían de la siguiente manera: En el caso de la conexión de tipo Azure Storage es posible añadir el nombre de la cuenta y la access key durante la creación, lo cual nos evita tener que añadirlo después, ya que ya le estamos dando la información que necesita para poder conectarse al recurso.

{
    "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "connection_name": {
            "defaultValue": "azureblob",
            "type": "String"
        },
        "location": {
            "type": "String"
        },
        "accountName": {
            "type": "String"
        },
        "accessKey": {
            "type": "String"
        }
    },
    "variables": {},
    "resources": [
        {
            "type": "Microsoft.Web/connections",
            "apiVersion": "2016-06-01",
            "name": "[parameters('connection_name')]",
            "location": "[parameters('location')]",
            "kind": "V1",
            "properties": {
                "displayName": "Azure Blob Connection",
                "parameterValues": {
                    "accountName": "[parameters('accountName')]",
                    "accessKey": "[parameters('accessKey')]"
                },
                "api": {
                    "id": "[concat(subscription().id,'/providers/Microsoft.Web/locations/',parameters('location'),'/managedApis/', parameters('connection_name'))]"
                }
            }
        }
    ]
}

A día de hoy no existe documentación como tal de los parámetros que necesita cada conexión, pero puedes seguir los pasos del artículo de mi compañero Derek, que te explica cómo obtenerlos. Existen otras conexiones, como veremos después, que no permiten añadir estos parámetros y necesitarán ser autorizadas en un segundo paso.

Aquí puedes echar un vistazo a todas las plantillas ARM descargadas y modificadas para este ejemplo.

2. Desplegar Azure Storage, CosmosDB y las API Connections.

Ahora que ya tienes las plantillas ARM de tu flujo con Logic Apps y las conexiones que utiliza, el siguiente paso es desplegar los recursos y las conexiones que hacen alusión a estos:

### Backend ###

terraform {
  backend "azurerm" {

  }
}

### Providers ###

provider "azurerm" {
  features {}
}

### Resources ###

#Random name
resource "random_pet" "service" {}

#Resource group
resource "azurerm_resource_group" "rg" {
  name     = random_pet.service.id
  location = var.location
}

#Azure Storage
resource "azurerm_storage_account" "storage" {
  name                = replace(random_pet.service.id, "-", "")
  resource_group_name = azurerm_resource_group.rg.name
  location            = azurerm_resource_group.rg.location

  account_tier             = "Standard"
  account_replication_type = "LRS"
  account_kind             = "StorageV2"

}

#CosmosDB
resource "azurerm_cosmosdb_account" "cosmosdbaccount" {
  name                = random_pet.service.id
  resource_group_name = azurerm_resource_group.rg.name
  location            = azurerm_resource_group.rg.location
  offer_type          = "Standard"
  kind                = "GlobalDocumentDB"

  consistency_policy {
    consistency_level       = "Session"
    max_interval_in_seconds = 10
    max_staleness_prefix    = 200
  }

  geo_location {
    location          = azurerm_resource_group.rg.location
    failover_priority = 0
  }

}

#CosmosDB - Database
resource "azurerm_cosmosdb_sql_database" "cosmosdb_db" {
  name                = var.cosmosdb_db
  resource_group_name = azurerm_resource_group.rg.name
  account_name        = azurerm_cosmosdb_account.cosmosdbaccount.name
  throughput          = 400
}

#CosmosDB - Container
resource "azurerm_cosmosdb_sql_container" "cosmosdb_container" {
  name                  = var.cosmosdb_container
  resource_group_name   = azurerm_resource_group.rg.name
  account_name          = azurerm_cosmosdb_account.cosmosdbaccount.name
  database_name         = azurerm_cosmosdb_sql_database.cosmosdb_db.name
  partition_key_path    = var.cosmosdb_partition_key
  partition_key_version = 1
  throughput            = 400
}


# ARM Template: Event grid connection
resource "azurerm_template_deployment" "eventgrid_connection" {
  name                = "eventgrid_connection"
  resource_group_name = azurerm_resource_group.rg.name
  deployment_mode     = "Incremental"
  template_body       = file("../../arm-templates/azureeventgrid-connection.json")

  parameters = {
    "location" = azurerm_resource_group.rg.location
  }
}

# ARM Template: Azure Blob Connection
resource "azurerm_template_deployment" "azureblob_connection" {
  name                = "azureblob_connection"
  resource_group_name = azurerm_resource_group.rg.name
  deployment_mode     = "Incremental"
  template_body       = file("../../arm-templates/azureblob-connection.json")

  parameters = {
    "location"    = azurerm_resource_group.rg.location
    "accountName" = azurerm_storage_account.storage.name
    "accessKey"   = azurerm_storage_account.storage.primary_access_key
  }
}

# ARM Template: CosmosDB Connection
resource "azurerm_template_deployment" "cosmosdb_connection" {
  name                = "cosmosdb_connection"
  resource_group_name = azurerm_resource_group.rg.name
  deployment_mode     = "Incremental"
  template_body       = file("../../arm-templates/cosmosdb-connection.json")

  parameters = {
    "location"    = azurerm_resource_group.rg.location
    "databaseAccount" = azurerm_cosmosdb_account.cosmosdbaccount.name
    "accessKey"   = azurerm_cosmosdb_account.cosmosdbaccount.primary_key
  }
}

El despliegue de la cuenta de almacenamiento y el CosmosDB sería de la forma habitual con Terraform. Sin embargo, para las conexiones a las APIs utilizo el recurso azurerm_template_deployment el cual te permite desplegar plantillas ARM desde Terraform, y a estas le paso como parámetros aquellas propiedades necesarias de los recursos creados fuera de estas.

Es importante que tengas en cuenta que este debe ser el primer paso cuando tu flujo depende de ciertas conexiones, ya que de lo contrario el mismo no funcionará correctamente. Para lanzar esta primera parte, puedes hacerlo con los siguientes comandos:

# 0. Variables
STORAGE_ACCOUNT_NAME="YOUR_STORAGE_ACCOUNT_NAME"
STORAGE_ACCOUNT_ACCESS_KEY="YOUR_STORAGE_ACCOUNT_ACCESS_KEY"

# 1. Create Azure Logic Apps Connections

cd terraform/1.api-connections

terraform init \
    -backend-config="storage_account_name=${STORAGE_ACCOUNT_NAME}" \
    -backend-config="container_name=video-indexer-flow" \
    -backend-config="key=api-connections.tfstate" \
    -backend-config="access_key=${STORAGE_ACCOUNT_ACCESS_KEY}"

terraform validate
terraform plan -var="azuread_domain=YOUR_DOMAIN.onmicrosoft.com" -out api-connections.tfplan
terraform apply api-connections.tfplan

Como ves, estoy haciendo uso de Azure Storage para almacenar los .tfstates , por lo que debes asignar una cuenta de almacenamiento durante terraform init. La variable azuread_domain, durante el terraform plan, se utiliza para generar los enlaces que dan acceso a través del portal de Azure a los recursos API Connections que vamos a crear. Cuando el proceso finalice, deberías de tener un output parecido al siguiente:

En el caso de la conexión de la cuenta de almacenamiento, o del CosmosDB, al haber añadido las credenciales como parte de la plantilla ARM no requerirán de ninguna acción más por nuestra parte, y aparecerán con el estado Connected.

Sin embargo, hay otros conectores como el de Office 365, Twitter, Event Grid, etcétera que requieren que te autentiques mediante un flujo OAuth que hace que no sea posible automatizar este paso. En el caso de la conexión con Event Grid deberás acceder al enlace devuelto en el output y autorizar la conexión:

Te darás cuenta rápido de aquellas conexiones que necesitan de autorización manual ya que estas tienen directamente en el apartado Edit API connection un botón llamado Authorize, en lugar de cajas de texto con la información que se espera, como ocurre en Azure Storage o CosmosDB.

3. Desplegar Azure Function y Azure Media Services.

Ahora que ya tenemos las conexiones, y los recursos asociados a ellas, lo siguiente que debemos desplegar es la Azure Function y Azure Media Services para obtener metadatos de los vídeos que subamos. Esta debe ser desplegada antes que el workflow de la Logic App, ya que este hace referencia a una función que todavía no existe y al intentar crear el flujo fallaría si no se desplega antes.

### Backend ###

terraform {
  backend "azurerm" {

  }
}

### Providers ###

provider "azurerm" {
  features {}
}

### Resources ###

#Random name
resource "random_pet" "service" {}

#Resource group
resource "azurerm_resource_group" "rg" {
  name     = random_pet.service.id
  location = var.location
}

#Azure Media Services
resource "azurerm_media_services_account" "ams" {
  name                = replace(random_pet.service.id, "-", "")
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name

  identity {
    type = "SystemAssigned"
  }

  storage_account {
    id         = data.terraform_remote_state.api_connections.outputs.storage_id
    is_primary = true
  }
}


#Azure Function

resource "azurerm_app_service_plan" "plan" {
  name                = random_pet.service.id
  resource_group_name = azurerm_resource_group.rg.name
  location            = azurerm_resource_group.rg.location
  kind                = "linux"
  reserved            = true

  sku {
    tier = "Standard"
    size = "S1"
  }
}


### Azure Function for AMS ###

#Azure Storage for Function App
resource "azurerm_storage_account" "storage" {
  name                     = replace(random_pet.service.id, "-", "")
  resource_group_name      = azurerm_resource_group.rg.name
  location                 = azurerm_resource_group.rg.location
  account_tier             = "Standard"
  account_replication_type = "LRS"
}

#Application Insights
resource "azurerm_application_insights" "insights" {
  name                = random_pet.service.id
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
  application_type    = "web"
}


resource "azurerm_function_app" "function" {
  name                       = random_pet.service.id
  location                   = azurerm_resource_group.rg.location
  resource_group_name        = azurerm_resource_group.rg.name
  app_service_plan_id        = azurerm_app_service_plan.plan.id
  storage_account_name       = azurerm_storage_account.storage.name
  storage_account_access_key = azurerm_storage_account.storage.primary_access_key

  version = "~3"

  app_settings = {
    "APPINSIGHTS_INSTRUMENTATIONKEY"        = azurerm_application_insights.insights.instrumentation_key
    "APPLICATIONINSIGHTS_CONNECTION_STRING" = azurerm_application_insights.insights.connection_string
    "FUNCTIONS_EXTENSION_VERSION"           = "~3"
    "FUNCTIONS_WORKER_RUNTIME"              = "dotnet"
    "WEBSITE_RUN_FROM_PACKAGE"              = 1
  }

  provisioner "local-exec" {
    #tested on mac only
    command = "az ams account sp create --account-name ${azurerm_media_services_account.ams.name} --resource-group ${azurerm_resource_group.rg.name} | jq 'with_entries( .key = \"AzureMediaServices__\"+.key)' > settings.json && az webapp config appsettings set -g ${azurerm_resource_group.rg.name} -n ${azurerm_function_app.function.name} --settings @settings.json"
  }
}

Una de las cosas interesantes que he utilizado para esta parte es la capacidad de consultar otros Terraform State para recuperar los outputs definidos.

#Data from API Connections

data "terraform_remote_state" "api_connections" {
  backend = "azurerm"
  config = {
    storage_account_name = "statestf"
    container_name       = "video-indexer-flow"
    key                  = "api-connections.tfstate"
    access_key           = var.remote_states_access_key
  }
}

Para ello está terraform_remote_state que en este caso lo utilizo para poder recuperar el grupo de recursos donde están alojadas las API Connections, la cuenta de almacenamiento, etcétera.

Por lo tanto, a la hora de ejecutar esta segunda parte, el comando tendrá que ser como el que sigue:

# 2. Create Azure Function and Azure Media Services account

cd terraform/2.azure-function

terraform init \
    -backend-config="storage_account_name=${STORAGE_ACCOUNT_NAME}" \
    -backend-config="container_name=video-indexer-flow" \
    -backend-config="key=azure-function.tfstate" \
    -backend-config="access_key=${STORAGE_ACCOUNT_ACCESS_KEY}"

terraform validate
terraform plan -var="remote_states_access_key=${STORAGE_ACCOUNT_ACCESS_KEY}" -out azure-function.tfplan
terraform apply azure-function.tfplan

# 2.1 IMPORTAT: You have to deploy AMSv3Indexer in the Azure Function

Como ves, en terraform plan añado la variable remote_states_access_key para poder conectarme al tfstate de Terraform que necesito para recuperar los valores.

Como último punto, en este escenario concreto, es importante tener en cuenta que es necesaria la información del service principal asociado a la cuenta de Azure Media Services. Por normal general, esta información se puede recuperar a través del siguiente comando:

az ams account sp create --account-name YOUR_AMS_NAME --resource-group YOUR_RESOURCE_GROUP

En este ejemplo esta parte se ha automatizado a través de lo que se conoce en Terraform como provisioner, utilizando un comando dentro de la definición de la Azure Function que actualizará los app settings con la información del service principal del AMS. Lo que hace es coger estas credenciales, transforma el objeto JSON, gracias a jq, en el formato que entiende la Azure Function y lo inserta dentro del apartado App Settings. Me parece importante que tengas en cuenta este punto ya que el comando se lanza en la máquina local que ejecuta el Terraform, por lo que si no estás usando Mac, que es donde se ha montado este ejemplo, puede ser que esta parte tengas que adaptarla.

IMPORTANTE: Una vez que tenemos la infraestructura para la Azure Function desplegada necesitamos publicar la lógica, que está en el proyecto AMSv3Indexer que forma parte del repositorio, antes de continuar. Puedes hacerlo desde Visual Studio Code, aunque lo realmente interesante sería que todo este proceso se lanzara desde otra herramienta más como GitHub Actions o Azure DevOps.

4. Desplegar workflow de Logic App enlazando todo lo anterior.

Por último, ya podemos desplegar la Logic App con la información de los dos despliegues anteriores:

### Backend ###

terraform {
  backend "azurerm" {

  }
}

### Providers ###

provider "azurerm" {
  features {}
}

### Resources ###

#Random name
resource "random_pet" "service" {}

#Resource group
resource "azurerm_resource_group" "rg" {
  name     = random_pet.service.id
  location = var.location
}

#ARM Template: Azure Logic App
resource "azurerm_template_deployment" "logic_app" {
  name                = random_pet.service.id
  resource_group_name = azurerm_resource_group.rg.name
  deployment_mode     = "Incremental"
  template_body       = file("../../arm-templates/logic-app-workflow.json")
  parameters = {
    "api_connections_location"       = data.terraform_remote_state.api_connections.outputs.resource_group_location
    "location"                       = azurerm_resource_group.rg.location
    "workflow_name"                  = random_pet.service.id
    "api_connections_resource_group" = data.terraform_remote_state.api_connections.outputs.resource_group_name
    "storage_resource_group"         = data.terraform_remote_state.api_connections.outputs.resource_group_name
    "storage_name"                   = data.terraform_remote_state.api_connections.outputs.storage_name
    "azure_function_resource_group"  = data.terraform_remote_state.azure_function.outputs.resource_group_name
    "azure_function_name"            = data.terraform_remote_state.azure_function.outputs.azure_function_name
  }
}

Para lanzar esta última parte puedes hacerlo de la siguiente forma:

# 3. Create Azure Logic App workflow

cd terraform/3.workflow

terraform init \
    -backend-config="storage_account_name=${STORAGE_ACCOUNT_NAME}" \
    -backend-config="container_name=video-indexer-flow" \
    -backend-config="key=workflow.tfstate" \
    -backend-config="access_key=${STORAGE_ACCOUNT_ACCESS_KEY}"

terraform validate
terraform plan -var="remote_states_access_key=${STORAGE_ACCOUNT_ACCESS_KEY}" -out workflow.tfplan
terraform apply workflow.tfplan

Todo el código de este ejemplo lo tienes en mi GitHub, y puedes seguir el despliegue usando el script.sh.

¡Saludos!