Azure Citadel
  • Blogs

  • Azure Arc
    • Overview
    • Azure Arc-enabled Kubernetes
      • Prereqs
      • Background
      • Deploy Cluster
      • Connect to Arc
      • Enable GitOps
      • Deploy Application
      • Enable Azure AD
      • Enforce Policy
      • Enable Monitoring
      • Enable Azure Defender
      • Enable Data Services
      • Enable Application Delivery
    • Azure Arc-enabled Servers
      • Prereqs
      • Scenario
      • Hack Overview
      • Azure Landing Zone
      • Arc Pilot resource group
      • Azure Monitoring Agent
      • Additional policy assignments
      • Access your on prem VMs
      • Create onboarding scripts
      • Onboarding using scripts
      • Inventory
      • Monitoring
      • SSH
      • Windows Admin Center
      • Governance
      • Custom Script Extension
      • Key Vault Extension
      • Managed Identity
    • Useful Links
  • Azure CLI
    • Install
    • Get started
    • JMESPATH queries
    • Integrate with Bash
  • Azure Landing Zones
    • ALZ Accelerator
      • Bootstrap
      • Prereqs
      • Elevate
      • Demote
      • Components
    • Deploy an Azure Landing Zone
      • Create an initial ALZ config
      • Test locally
      • Run through the CI/CD workflow
    • Example Library Configs
      • Azure Landing Zone library
      • Azure Landing Zone library with overrides
  • Azure Lighthouse
    • Minimal Lighthouse definition
    • Using service principals
    • Privileged Identity Management
  • Azure Policy
    • Azure Policy Basics
      • Policy Basics in the Azure Portal
      • Creating Policy via the CLI
      • Deploy If Not Exists
      • Management Groups and Initiatives
    • Creating Custom Policies
      • Customer scenario
      • Policy Aliases
      • Determine the logic
      • Create the custom policy
      • Define, assign and test
  • Marketplace
    • Introduction
      • Terminology
      • Offer Types
    • Partner Center
    • Offer Type
    • Publish a VM Offer HOL
      • Getting Started
      • Create VM Image
      • Test VM Image
      • VM Offer with SIG
      • VM Offer with SAS
      • Publish Offer
      • Other VM Resources
    • Publish a Solution Template HOL
      • Getting Started
      • Create ARM Template
      • Validate ARM Template
      • Create UI Definition
      • Package Assets
      • Publish Offer
    • Publish a Managed App HOL
      • Getting Started
      • Create ARM Template
      • Validate ARM Template
      • Create UI Definition
      • Package Assets
      • Publish Offer
    • Managed Apps with AKS HOL
    • Other Managed App Resources
    • SaaS Offer HOLs
    • SaaS Offer Video Series
      • Video 1 - SaaS Offer Overview
      • Video 2 - Purchasing a SaaS Offer
      • Video 3 - Purchasing a Private SaaS Plan
      • Video 4 - Publishing a SaaS Offer
      • Video 5 - Publishing a Private SaaS Plan
      • Video 6 - SaaS Offer Technical Overview
      • Video 7 - Azure AD Application Registrations
      • Video 8 - Using the SaaS Offer REST Fulfillment API
      • Video 9 - The SaaS Client Library for .NET
      • Video 10 - Building a Simple SaaS Landing Page in .NET
      • Video 11 - Building a Simple SaaS Publisher Portal in .NET
      • Video 12 - SaaS Webhook Overview
      • Video 13 - Implementing a Simple SaaS Webhook in .NET
      • Video 14 - Securing a Simple SaaS Webhook in .NET
      • Video 15 - SaaS Metered Billing Overview
      • Video 16 - The SaaS Metered Billing API with REST
  • Microsoft Fabric
    • Theory
    • Prereqs
    • Fabric Capacity
    • Set up a Remote State
    • Create a repo from a GitHub template
    • Configure an app reg for development
    • Initial Terraform workflow
    • Expanding your config
    • Configure a workload identity
    • GitHub Actions for Microsoft Fabric
    • GitLab pipeline for Microsoft Fabric
  • Packer & Ansible
    • Packer
    • Ansible
    • Dynamic Inventories
    • Playbooks & Roles
    • Custom Roles
    • Shared Image Gallery
  • Partner Admin Link
    • Understanding PAL
    • User IDs & PAL
    • Service principals & PAL
    • CI/CD pipelines & PAL
    • Creating a dedicated PAL service principal
    • Azure Lighthouse & PAL
    • PAL FAQ
  • REST API
    • REST API theory
    • Using az rest
  • Setup
  • Sovereign Landing Zones
    • ALZ Accelerator
      • Prereqs
      • Elevate
      • Bootstrap
      • Demote
      • Components
    • Deploy Sovereign Landing Zone
      • Create an initial SLZ config
      • Test locally
      • Run through the CI/CD workflow
    • Example Library Configs
      • Sovereign Landing Zone
      • Sovereign Landing Zone library with overrides
  • Terraform
    • Fundamentals
      • Initialise
      • Format
      • Validate
      • Plan
      • Apply
      • Adding resources
      • Locals and outputs
      • Managing state
      • Importing resources
      • Destroy
    • Get set up for Terraform
      • Cloud Shell
      • macOS
      • Windows with PowerShell
      • Windows with Ubuntu in WSL2
    • Using AzAPI
      • Using the REST API
      • azapi_resource
      • Removing azapi_resource
      • azapi_update_resource
      • Data sources and outputs
      • Removing azapi_update_resource
  • Virtual Machines
    • Azure Bastion with native tools & AAD
    • Managed Identities

  • About
  • Archive
  1. Home
  2. Sovereign Landing Zones
  3. Deploy Sovereign Landing Zone
  4. Test locally
Test locally
Test locally
Deploy Sovereign Landing Zone
Create an initial SLZ config
Test locally
Run through the CI/CD workflow
  • Next

Test locally

See how to test your changes locally so that you can go into the CI/CD process confident that your commit will pass the Continuous Integration tests.

Table of Contents

  • Next

Overview

On this page you will locally test your config will pass the test within the continuous integration (CI) workflow. These test that your config meets minimum standards

  • formatting needs to be compliant with the canonical format and style based on gofmt, as tested and corrected by the terraform fmt command
  • runs the terraform init command to confirm that the various providers and modules are coherent, including the semantic version constraints
  • passes the terraform validate command which ensures that your config is syntactically sound
  • successfully runs terraform plan to generate a plan

The various providers in your config, such as azurerm and azapi, have additional checks within the resource types, so this will surface any additional issues in the config or current environment.

The plans are usually run by the GitHub Actions workflows and those workflows rely on the GitHub Actions variables to define the subscription ID and backend, i.e. the storage account container and key (blob) file. You’ll see how you can override the backend with a terraform_override.tf file to enable local testing without exporting environment variables.

⚠️ You must not attempt to run terraform apply locally. You will use the CI/CD workflow in the next page instead.

Override the backend

The terraform block in the terraform.tf file includes an empty backend.

  backend "azurerm" {}

The workflow’s will use the BACKEND_AZURE_* environment variables within the terraform init steps so that the managed identities write the remote state file to the correct storage account and container.

Before we progress we want to make sure that your config will pass the CI checks, so we will locally override the backend so that you can run the tests at the CLI.

Using a local backend is the easiest approach for basic testing of a config. On the positive side it will check to ensure that a plan can be created - and nothing more. That is sufficient to be able to commit and publish a branch assuming the fmt and validate checks have also been met.

On the negative side you do not get a true idea what the proposed changes will be as you are not using the same state file as the CI/CD workflows. For that you need the remote backend.

This approach does not need any RBAC role assignment or network settings on the storage account to work as you are only creating a local terraform.tfstate file.

  1. Create a terraform_override.tf

    terraform {
      backend "local" {
        path = "terraform.tfstate"
      }
    }
    

    ℹ️ The file should be greyed out in Visual Studio Code’s explorer pane as this filename is included in the .gitignore. It will not be included in any git commits.

  2. Set the ARM_SUBSCRIPTION_ID env var

    The config is also expecting a subscription ID for the main azurerm provider block. Export the environment variable, setting it to the management subscription GUID.

    export ARM_SUBSCRIPTION_ID="managementSubscriptionId"
    

    Alternatively, here is a similar command that exports the subscription ID based on the name for the subscription.

    export ARM_SUBSCRIPTION_ID=$(az account list --query "[?name == 'Management']".id --output tsv)
    

    Regardless of which approach you use, make sure that you set the subscription ID or the name of the subscription.

Using the same remote backend as the CI/CD pipelines is a fantastic way to make changes to a config locally and then view the diff plan created by terraform.

Setting this up takes a little more work and access but it is worth the effort.

⚠️ Note that this option requires a privileged role. The RBAC role assignment and storage account networking changes weaken security. Check the warnings against those steps.

  1. Set the variables using the GitHub Actions variables

    You will need to be authenticated in the GitHub CLI (gh auth status) and you must also have jq installed.

    vars=$(gh variable list --json name,value | jq 'map({(.name): .value}) | add')
    sub="$(jq -r .AZURE_SUBSCRIPTION_ID <<< $vars)"
    rg="$(jq -r .BACKEND_AZURE_RESOURCE_GROUP_NAME <<< $vars)"
    sa="$(jq -r .BACKEND_AZURE_STORAGE_ACCOUNT_NAME <<< $vars)"
    container="$(jq -r .BACKEND_AZURE_STORAGE_ACCOUNT_CONTAINER_NAME <<< $vars)"
    
  2. Create the override file

    cat > terraform_override.tf << EOF
    # Overrides to enable local terraform plan
    
    terraform {
      backend "azurerm" {
        use_azuread_auth     = true
        subscription_id      = "$sub"
        resource_group_name  = "$rg"
        storage_account_name = "$sa"
        container_name       = "$container"
        key                  = "terraform.tfstate"
      }
    }
    
    provider "azurerm" {
      subscription_id = "$(jq -r .AZURE_SUBSCRIPTION_ID <<< $vars)"
    }
    EOF
    echo "Created terraform_override.tf using the GitHub Actions variables"
    
  3. Assign the Storage Blob Data Reader role

    ⚠️ This role will give access to view any sensitive values stored in the state file.

    id="/subscriptions/$sub/resourceGroups/$rg/providers/Microsoft.Storage/storageAccounts/$sa"
    scope="$id/blobServices/default/containers/$container"
    az role assignment create --role "Storage Blob Data Reader" --scope $scope --assignee $(az ad signed-in-user show --query id -otsv)
    
  4. Allow my public IP access to the storage account

    ⚠️ This increases the attack surface of the storage account by making it vulnerable to potential IP address spoofing attacks.

    address=$(curl -s ipinfo.io/ip) # address=$(curl -s ipinfo.io/ip | cut -d. -f1-2).0.0/16
    az storage account update --ids "$id" --public-network-access Enabled --default-action Deny
    az storage account network-rule add --subscription "$sub" --resource-group "$rg" --account-name  "$id" --ip-address "$address"
    

Format

Check the formatting is compliant with the canonical format and style based on gofmt standards.

terraform fmt

This command will automatically correct issues where possible, listing the files it has modified.

There is no command output if all of the .tf files are already compliant.

Initialise

Initialise Terraform to confirm that the various providers and modules are coherent, including the semantic version constraints.

terraform init

Validate the files

Confirm that the config is syntactically sound.

terraform validate

Correct any errors until terraform validate succeeds.

Success! The configuration is valid.

Plan (optional)

⚠️ REMEMBER - DO NOT RUN terraform apply at the CLI for these labs! The apply will be done via the CI/CD pipelines on the next page.

Assuming that your ID has access to view the resources in both Azure and GitHub then you can test to see if the plan will succeed.

Run a plan using the local backend override.

terraform plan

The output will show that terraform plans to create over 600 resources.

Note that running terraform plan locally just checks the code and is not using the remote state in the storage account as we overrode the backend to use a local backend instead..

The terraform plan outputs in the CI/CD pipelines will provide the accurate and definitive diff and should always be properly reviewed.

Run a plan using the remote state file.

terraform plan -lock=false

Note that the Storage Blob Data Reader role cannot create leases on the blobs which is the mechanism used for state locking.

Next

If the tests all look good then your config should pass the CI tests. You’re done on this page.

On the next page we will run through the standard branch based workflow to commit your changes into a new branch and submit a pull request to trigger the CI/CD pipelines.

Source: https://www.azurecitadel.com/slz/deploy/test/
Published: 10 Oct 2025
Printed:
Create an initial SLZ config Test locally Run through the CI/CD workflow