Deploy Integration Resources¶
How a workload team deploys integration resources (Logic Apps, Function Apps, Container Apps, Data Factory) into its spoke with the integration modules: the spoke outputs file, the configuration, and the reusable deployment workflow with its safety rules.
Reference design
The pipeline in the reference design is GitHub Actions with a reusable workflow. Your platform may use other tooling or other names. Ask your platform team for the template, the runner group and the release to use.
Overview¶
flowchart LR
a["1. Spoke outputs<br/>one file per environment"] --> b["2. Your configuration<br/>module call + maps"]
b --> c["3. Review and plan<br/>pull request, plan-only run"]
c --> d["4. Manual run<br/>reusable workflow: apply"]
d --> e["5. Deploy your application code"]
Step 1: get the spoke outputs¶
The modules need the facts of your spoke: resource group, network and subnets, Key Vault, storage account and Log
Analytics workspace. These are kept in a variable file with one spoke_config object, one file per environment, for example
config/spoke-outputs-<environment>.tfvars. It is based on the outputs of the spoke deployment, so that your
configuration stays aligned with the spoke and you do not retype values.
spoke_config = {
name = "<workload>-<environment>"
subscription_id = "<subscription-id>"
resource_group_name = "<resource-group>"
location = "West Europe"
tenant_id = "<tenant-id>"
environment = "<environment>"
key_vault_id = "<key-vault-resource-id>"
key_vault_name = "<key-vault-name>"
storage_account_id = "<storage-account-resource-id>"
storage_account_name = "<storage-account-name>"
virtual_network_id = "<vnet-resource-id>"
virtual_network_name = "<vnet-name>"
subnet_ids = { "<subnet-name>" = "<subnet-resource-id>" }
subnet_names = { "<subnet-name>" = "<subnet-name>" }
log_analytics_workspace_id = "<workspace-resource-id>"
log_analytics_workspace_name = "<workspace-name>"
}
The field list is on Modules. Ask your platform team how your spoke produces this file. Never edit the values by hand when the spoke changes: take the new outputs.
Step 2: write the configuration¶
Call the root module and fill the maps you need (logic_apps, function_apps with service_plans,
container_apps_environment, data_factories), see Modules. Give every environment its own configuration
values through the variable files, and keep the code the same.
Step 3: review and plan¶
Open a pull request and have it reviewed. Then start the deployment workflow (step 4) with apply switched off: it runs
terraform fmt, init, validate and plan and shows the plan. Read it. You expect resources in your spoke, no changes
to resources you did not touch, and no unexpected deletes.
Step 4: run the reusable workflow¶
Deployments do not start automatically. A person starts the deployment workflow by hand and chooses the environment and whether to apply.
The reference design offers a reusable workflow, so every workload repository uses the same, reviewed deployment path. The workload repository holds a small caller:
name: Terraform deployment
on:
workflow_dispatch:
inputs:
environment:
description: Environment to plan or deploy
required: true
type: choice
options: [dev, tst, acc, prd]
default: dev
enableApply:
description: Apply the plan
required: true
type: boolean
default: false
runnerGroup:
description: Self-hosted runner group
required: true
type: string
default: <runner-group>
permissions:
actions: read
contents: read
id-token: write
jobs:
deploy:
uses: <organisation>/<platform-repository>/.github/workflows/terraform-deploy.yml@<release-tag>
with:
environment: ${{ inputs.environment }}
enable_apply: ${{ inputs.enableApply }}
runner_group: ${{ inputs.runnerGroup }}
module_owner: <organisation>
module_repository: <module-repository>
secrets:
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
MODULE_GITHUB_APP_ID: ${{ vars.<module-app-id-variable> }}
MODULE_GITHUB_APP_PRIVATE_KEY: ${{ secrets.<module-app-key-secret> }}
| Input | Required | Purpose |
|---|---|---|
environment |
yes | Selects dev, tst, acc or prd and the matching repository environment |
runner_group |
yes | The self-hosted runner group (runners inside the network) |
module_owner, module_repository |
yes | Owner and name of the private repository that holds the Terraform modules |
enable_apply |
no | Default false. Apply is allowed only from the main branch. |
terraform_version |
no | Terraform CLI version. The reusable workflow has a default. |
Secrets and permissions:
- The caller passes the Azure sign-in values (
AZURE_CLIENT_ID,AZURE_TENANT_ID,AZURE_SUBSCRIPTION_ID) from the selected environment. Sign-in to Azure uses OpenID Connect (theid-token: writepermission), not a stored password. - A GitHub App identity (ID and private key) lets the workflow download the private Terraform modules. Configure the ID as a repository variable and the key as a repository secret.
- The called workflow cannot grant permissions that the caller withheld, so
actions: read,contents: readandid-token: writemust be in the caller. The caller repository must be allowed to use reusable workflows from the repository that hosts them.
Safety rules of the workflow¶
- The selected environment needs a backend file (
config/<environment>.azurerm.tfbackend) and a spoke outputs file (config/spoke-outputs-<environment>.tfvars), plus an existing repository environment. - Format, init, validate and plan always run before an apply.
- Apply is off by default and is rejected unless the workflow runs from
main. - An apply to
accorprdalso requires at least one required reviewer on that environment. - One call deploys one environment. Promotion and order between environments are yours.
- Pin the workflow to a release tag or commit, not to a moving branch. Your platform team publishes and announces releases.
Step 5: deploy your application¶
Terraform deploys the empty service. You deploy your code with your own pipeline: workflows to a Logic App, code to a Function App, images to the registry and then a new revision of a Container App. The resources are reachable only through private paths, so your pipeline must run on a runner inside the network (Build agents).
After the deployment¶
- Resources can reach other resources in the spoke through VNet integration. Inbound access is by private endpoint only.
- Request firewall rules for anything outside your spoke (Request and change).
- Do not change Terraform-managed resources in the portal. The next deployment undoes it.