Azure Citadel
  • Blogs

  • ARM
  • Azure Arc
    • Overview
    • 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
    • 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
    • Useful Links
  • Azure CLI
    • Install
    • Get started
    • JMESPATH queries
    • Integrate with Bash
  • Azure Landing Zones
    • Prereqs
    • Day 1
      • Azure Baristas
      • Day 1 Challenge
    • Day 2
      • Example
      • Day 2 Challenge
    • Day 3
      • Day 3 Challenge
    • Useful Links
  • 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
  • Azure Stack HCI
    • Overview
    • Useful Links
    • Updates from Microsoft Ignite 2022
  • 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
    • Lighthouse and Partner Admin Link
      • Microsoft Cloud Partner Program
      • Combining Lighthouse and PAL
      • Minimal Lighthouse definition
      • Using service principals
      • Privileged Identity Management
    • Useful Links
  • REST API
    • REST API theory
    • Using az rest
  • Setup
  • Terraform
    • Fundamentals
      • Initialise
      • Format
      • Validate
      • Plan
      • Apply
      • Adding resources
      • Locals and outputs
      • Managing state
      • Importing resources
      • Destroy
    • Working Environments 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. Microsoft Fabric
  3. Demo

Table of Contents

  • Pre-demo checks
  • Intro
  • Fabric Capacity
  • User context
  • Repo & initial run
  • MCP servers
  • Fabric tenant settings
  • Managed identity and backend
  • Authorisations
    • Fabric
    • Azure RBAC
    • Entra App Roles
  • GitHub
  • Links
  • Cleanup
  • Confirmation

Demo

Demo script and code blocks

Pre-demo checks

  1. Portals (as admin ID)

    • Entra portal
    • Azure portal
    • Fabric portal
  2. Azure CLI

    az account set --subscription "Terraform Fabric"
    az account show
    
  3. GitHub

    gh auth status
    
  4. Fabric CLI

    fab auth status
    
  5. Docker

    Check Docker Desktop is already running. Check the Terraform MCP server will run.

    docker run --interactive --rm hashicorp/terraform-mcp-server
    

    This will also download the image and cache it.

    If you see Terraform MCP Server running on stdio then all good. Press Ctrl+D to exit to the prompt.

Intro

  1. Use the https://azurecitadel.com/fabric/theory page to cover Fabric Admin vs workspace level CI/CD integration.
  2. Then move to Fabric Capacities - F-SKU, T-SKU, P-SKU

Fabric Capacity

  1. List the available capacities.

    fab ls -l .capacities
    
  2. Create an F-SKU

    az group create --name "rg-fabric" --location "UK South"
    upn=$(az ad signed-in-user show --query userPrincipalName -otsv)
    az fabric capacity create --capacity-name "example" --resource-group "rg-fabric" --location "UK South" --sku "{name:F2,tier:Fabric}" --administration "{members:[${upn}]}"
    
  3. Relist the available capacities

    fab ls -l .capacities
    

    Whilst that is running, go to the Azure portal and view the administrators for the fabric capacity.

  4. Get full output for an F-SKU (optional)

    fab get .capacities/example.Capacity -q . | jq .
    

User context

Can follow the https://registry.terraform.io/providers/microsoft/fabric/latest/docs/guides/auth_app_reg_user page, or use the commands below. (Is this needed, or has the Go SDK for Fabric been updated?)

  1. Create the app reg

    Note that this needs to meet naming conventions now: https://aka.ms/identifier-uri-formatting-error

    name=fabric_terraform_provider
    uri="api://$(az account show --query tenantId -otsv)/$name"
    
    az ad app create --display-name $name --identifier-uris $uri
    az ad app update --id $uri --required-resource-accesses '[{"resourceAppId":"00000003-0000-0000-c000-000000000000","resourceAccess":[{"id":"e1fe6dd8-ba31-4d61-89e7-88639da4683d","type":"Scope"},{"id":"b340eb25-3456-403f-be2f-af7a0d370277","type":"Scope"}]},{"resourceAppId":"00000009-0000-0000-c000-000000000000","resourceAccess":[{"id":"4eabc3d1-b762-40ff-9da5-0e18fdf11230","type":"Scope"},{"id":"b2f1b2fa-f35c-407c-979c-a858a808ba85","type":"Scope"},{"id":"445002fb-a6f2-4dc1-a81e-4254a111cd29","type":"Scope"},{"id":"8b01a991-5a5a-47f8-91a2-84d6bfd72c02","type":"Scope"}]}]'
    az ad app update --id $uri --set api='{"acceptMappedClaims":null,"knownClientApplications":[],"oauth2PermissionScopes":[{"adminConsentDescription":"Allows connection to backend services for Microsoft Fabric Terraform Provider","adminConsentDisplayName":"Microsoft Fabric Terraform Provider","id":"1ca1271c-e2c0-437c-af9a-3a92e745a24d","isEnabled":true,"type":"User","userConsentDescription":"Allows connection to backend services for Microsoft Fabric Terraform Provider","userConsentDisplayName":"Microsoft Fabric Terraform Provider","value":"access"}],"preAuthorizedApplications":[],"requestedAccessTokenVersion":null}'
    az ad app update --id $uri --set api='{"acceptMappedClaims":null,"knownClientApplications":[],"oauth2PermissionScopes":[{"adminConsentDescription":"Allows connection to backend services for Microsoft Fabric Terraform Provider","adminConsentDisplayName":"Microsoft Fabric Terraform Provider","id":"1ca1271c-e2c0-437c-af9a-3a92e745a24d","isEnabled":true,"type":"User","userConsentDescription":"Allows connection to backend services for Microsoft Fabric Terraform Provider","userConsentDisplayName":"Microsoft Fabric Terraform Provider","value":"access"}],"preAuthorizedApplications":[{"appId":"871c010f-5e61-4fb1-83ac-98610a7e9110","delegatedPermissionIds":["1ca1271c-e2c0-437c-af9a-3a92e745a24d"]},{"appId":"00000009-0000-0000-c000-000000000000","delegatedPermissionIds":["1ca1271c-e2c0-437c-af9a-3a92e745a24d"]},{"appId":"1950a258-227b-4e31-a9cf-717495945fc2","delegatedPermissionIds":["1ca1271c-e2c0-437c-af9a-3a92e745a24d"]},{"appId":"04b07795-8ddb-461a-bbee-02f9e1bf7b46","delegatedPermissionIds":["1ca1271c-e2c0-437c-af9a-3a92e745a24d"]}],"requestedAccessTokenVersion":null}'
    az ad app owner add --id $uri --owner-object-id $(az ad signed-in-user show --query id -otsv)
    az ad app show --id $uri --output jsonc
    echo "App Registration created. Authenticate with:"
    echo "az login --scope $uri/.default"
    
  2. Microsoft Entra admin center > App registrations > My applications

    • API permissions
    • Exposed API
    • Pre-authorised apps (PowerShell, Azure CLI, Power BI)
  3. Authenticate

    az login --scope api://$(az account show --query tenantId -otsv)/fabric_terraform_provider/.default
    
  4. View token

    jq .AccessToken < ~/.azure/msal_token_cache.json
    

Repo & initial run

  1. https://github.com/richeney/fabric_terraform_provider_quickstart

  2. Create to “fabric_demo”

  3. Clone

    cd ~/git
    git clone https://github.com/richeney/fabric_demo demo
    cd ~/git/demo
    code .
    
  4. Browse

  5. Rename and modify example-terraform.tfvar

  6. fmt > init > validate

  7. apply

    terraform plan -var-file=test.tfvars
    

Browse https://aka.ms/terraform/fabric

MCP servers

  1. Add Microsoft Learn Docs MCP server

  2. Run Docker Desktop

  3. Create .vscode/mcp.json

    {
        "servers": {
            "terraform": {
                "type": "stdio",
                "command": "docker",
                "args": [
                    "run",
                    "--interactive",
                    "--rm",
                    "hashicorp/terraform-mcp-server"
                ]
            }
        }
    }
    
  4. Prompts

    #terraform Describe the fabric workspace RBAC assignment resource
    
    add in the azuread provider, a string variable for group, and a data source for azuread_group
    
    what role assignment resources are there in the fabric provider?
    
    add the fabric_workspace_role_assignment for the example workspace and selected group and Contributor role
    
  5. Rerun fmt > init > validate > plan

  6. Apply

    Browse Fabric portal including workspace settings.

  7. Destroy

    If Apply is successful then Destroy.

  8. ⚠️ Update prod.tfvars

    • Set fabric_capacity_name = example
    • Add group = Finance

Ready to move to CI/CD.

Fabric tenant settings

Fabric Admin Portal > Tenant Settings > Developer settings. Allows either all identities or a specified security group.

  1. Entra security group for Microsoft Fabric Workload Identities

    fabric_group_name="Microsoft Fabric Workload Identities"
    fabric_group_description="Service Principals and Managed Identities used for Fabric automation."
    fabric_group_mail_nickname="FabricWorkloadIdentities"
    az ad group create --display-name "$fabric_group_name" --description "$fabric_group_description" --mail-nickname "$fabric_group_mail_nickname"
    fabric_group_id=$(az ad group show --group "Microsoft Fabric Workload Identities" --query id -otsv)
    echo "Created security group $fabric_group_name"
    
  2. Update tenant settings

    body=$(jq -nc --arg oid "$fabric_group_id" --arg name "$fabric_group_name" '{"enabled":true,"canSpecifySecurityGroups":true,"enabledSecurityGroups":[{"graphId":$oid,"name":$name}]}')
    jq . <<< $body
    fab api --method post admin/tenantsettings/ServicePrincipalAccessGlobalAPIs/update -i "$body"
    fab api --method post admin/tenantsettings/ServicePrincipalAccessPermissionAPIs/update -i "$body"
    fab api --method post admin/tenantsettings/AllowServicePrincipalsCreateAndUseProfiles/update -i "$body"
    

    The Fabric CLI is great for Fabric REST API calls.

    fab api --method get admin/tenantsettings --query "text.tenantSettings[?tenantSettingGroup == 'Developer settings']" | jq .
    

Managed identity and backend

  1. Create storage account and managed identity

    terraform_resource_group_name="rg-terraform"
    location="uksouth"
    managed_identity_name="mi-terraform"
    storage_account_prefix="saterraform"
    management_subscription_id="73568139-5c52-4066-a406-3e8533bb0f15"
    
    az account set --subscription $management_subscription_id
    az group create --name $terraform_resource_group_name --location $location
    az identity create --name $managed_identity_name --resource-group $terraform_resource_group_name --location $location
    managed_identity_object_id=$(az identity show --name $managed_identity_name --resource-group $terraform_resource_group_name --query principalId -otsv)
    managed_identity_client_id=$(az identity show --name $managed_identity_name --resource-group $terraform_resource_group_name --query clientId -otsv)
    storage_account_name="$storage_account_prefix$(az group show --name $terraform_resource_group_name --query id -otsv | sha1sum | cut -c1-8)"
    az storage account create --name $storage_account_name --resource-group $terraform_resource_group_name --location $location --min-tls-version TLS1_2 --sku Standard_LRS --https-only true --default-action "Allow" --public-network-access "Enabled" --allow-shared-key-access false --allow-blob-public-access false
    storage_account_id=$(az storage account show --name $storage_account_name --resource-group $terraform_resource_group_name --query id -otsv)
    az storage account blob-service-properties update --account-name $storage_account_name --enable-versioning --enable-delete-retention   --delete-retention-days 7
    az storage container create --name prod --account-name $storage_account_name --auth-mode login
    az role assignment create --role "Storage Blob Data Contributor" --assignee $managed_identity_object_id --scope "$storage_account_id/blobServices/default/containers/prod"
    az storage container create --name dev --account-name $storage_account_name --auth-mode login
    az role assignment create --role "Storage Blob Data Contributor" --assignee $(az ad signed-in-user show --query id -otsv) --scope "$storage_account_id/blobServices/default/containers/dev"
    
  2. Create a backend.tf

    cat - <<BACKEND > backend.tf
    terraform {
      backend "azurerm" {
        subscription_id      = "$management_subscription_id"
        resource_group_name  = "terraform"
        storage_account_name = "$storage_account_name"
        container_name       = "test"
        key                  = "terraform.tfstate"
        use_azuread_auth     = true
      }
    }
    BACKEND
    

Authorisations

Fabric

  1. Add to the Entra security group for Fabric

    az ad group member add --group "$fabric_group_name" --member-id "$managed_identity_object_id"
    
  2. Add as administrator on the Fabric Capacity

    fabric_resource_id=$(az fabric capacity show --name "example" --resource-group "rg-fabric" --query id -otsv)
    admins=$(az fabric capacity show --ids $fabric_resource_id --query administration.members --output json | jq --arg oid "$managed_identity_object_id" '. += [$oid]')
    az fabric capacity update --ids $fabric_resource_id --administration "{\"members\": $admins}"
    

    Show the administrators on the Fabric Capacity - note that it is a UPN for users and object ID for workload identities.

    Alternatively:

    • assign the Fabric Administrator role to the Entra security group when creating (use the Microsoft Graph)
    • assign Contributor to the Fabric Capacity in the Fabric Admin Portal

    Effectively choose from Entra RBAC (admin on all), Azure RBAC (admin on specified), or Fabric RBAC (contributor on specified).

Azure RBAC

  1. Azure RBAC

    scope="/subscriptions/73568139-5c52-4066-a406-3e8533bb0f15"
    az role assignment create --role "Contributor" --scope $scope --assignee-object-id $managed_identity_object_id --assignee-principal-type ServicePrincipal
    

    Show in Azure portal.

Entra App Roles

  1. Entra app roles

    App roles are hidden away, and only available via the REST API today.

    graph_app_id="00000003-0000-0000-c000-000000000000"
    graph_object_id=$(az ad sp show --id $graph_app_id --query id -otsv)
    
    for role in User.Read.All Group.Read.All
    do
      app_role_id=$(az ad sp show --id $graph_app_id --query "appRoles[?value == '"$role"'].id" -otsv)
      body=$(jq -nc --arg graph "$graph_object_id" --arg mi "$managed_identity_object_id" --arg role "$app_role_id" '{principalId:$mi,resourceId:$graph,appRoleId:$role}')
      echo "Adding app role $role:"
      jq . <<< $body
      az rest --method post --uri "https://graph.microsoft.com/v1.0/servicePrincipals/${managed_identity_object_id}/appRoleAssignments" --body "$body"
    done
    

    Show in Entra portal under Enterprise Apps (Application type = Managed Identities). Other roles:

    az ad sp show --id 00000003-0000-0000-c000-000000000000 --query "appRoles[].value" -oyamlc
    

GitHub

  1. Commit the changes and push

    Do it in vscode, or:

    git add .github/workflows/terraform.yml
    git commit -m "Added Terraform workflow"
    git push
    
  2. View

    gh repo view --web
    
  3. Browse the .github/workflows/terraform.yml file

  4. Create GitHub Actions variables

    terraform_resource_group_name="rg-terraform"
    managed_identity_name="mi-terraform"
    storage_account_prefix="saterraform"
    subscription_id="73568139-5c52-4066-a406-3e8533bb0f15"
    managed_identity_client_id=$(az identity show --name $managed_identity_name --resource-group $terraform_resource_group_name --query clientId -otsv)
    storage_account_name="saterraform$(az group show --name $terraform_resource_group_name --query id -otsv | sha1sum | cut -c1-8)"
    
    gh variable set ARM_TENANT_ID --body "$(az account show --query tenantId -otsv)"
    gh variable set ARM_CLIENT_ID --body "$managed_identity_client_id"
    gh variable set ARM_SUBSCRIPTION_ID --body "$subscription_id"
    gh variable set BACKEND_AZURE_SUBSCRIPTION_ID --body "$subscription_id"
    gh variable set BACKEND_AZURE_RESOURCE_GROUP_NAME --body "$terraform_resource_group_name"
    gh variable set BACKEND_AZURE_STORAGE_ACCOUNT_NAME --body "$storage_account_name"
    gh variable set BACKEND_AZURE_STORAGE_ACCOUNT_CONTAINER_NAME --body "prod"
    gh variable set TFVARS_FILE --body "prod.tfvars"
    

    View in the Settings > GitHub Actions variables

  5. Create federated credential in the portal

    Key Value
    org richeney
    repo fabric_demo
    entity Branch
    Branch main
    name github
    subject=$(gh repo view --json nameWithOwner --template '{{printf "repo:%s:ref:refs/heads/main" .nameWithOwner}}')
    az identity federated-credential create --name github --identity-name $managed_identity_name --resource-group $terraform_resource_group_name --audiences "api://AzureADTokenExchange" --issuer "https://token.actions.githubusercontent.com" --subject "$subject"
    echo "Added federated credential."
    
  6. Run the workflow

    gh workflow run terraform.yml
    
  7. View the workflow

    gh workflow view terraform.yml --web
    

Links

  • Terraform Provider for Microsoft Fabric: #1 Accelerating first steps using the CLIs

  • Terraform Provider for Microsoft Fabric: #2 Using MCP servers and Fabric CLI to help define your fabric resources

  • Terraform Provider for Microsoft Fabric: #3 Creating a workload identity with Fabric permissions

  • Terraform Provider for Microsoft Fabric: #4 Deploying a fabric config with Terraform in GitHub Actions

  • https://github.com/richeney/terraform_fabric_administrator_reference

  • https://azurecitadel.com/fabric

Cleanup

  1. Terraform destroy, or remove workspace from Fabric

    terraform destroy
    
  2. Delete repo

    cd ~/git
    rm -fr ~/git/demo
    
    
  3. Delete repo from GitHub

    gh repo delete --yes richeney/fabric_demo
    
  4. Remove MCP server from Visual Studio Code’s user settings

  5. Delete terraform and fabric resource groups

    az group delete --yes --no-wait --name rg-terraform
    az group delete --yes --no-wait --name rg-fabric
    
  6. Remove Fabric developer settings

    body=$(jq -nc '{"enabled":false,"canSpecifySecurityGroups":false}')
    fab api --method post admin/tenantsettings/ServicePrincipalAccessGlobalAPIs/update -i "$body"
    fab api --method post admin/tenantsettings/ServicePrincipalAccessPermissionAPIs/update -i "$body"
    fab api --method post admin/tenantsettings/AllowServicePrincipalsCreateAndUseProfiles/update -i "$body"
    
  7. Remove Entra group

    az ad group delete --group "Microsoft Fabric Workload Identities"
    
  8. Remove app reg

    az ad app delete --id api://$(az account show --query tenantId -otsv)/fabric_terraform_provider
    
  9. Log out of az and fab and delete MSAL token cache (optional)

    fab auth logout
    az logout
    rm ~/.azure/msal_token_cache.json
    
  10. Clear the most recently used list in Azure Portal

  11. Check Workspaces in Fabric Admin Portal. May need to clean up Orphaned workspaces.

Confirmation

az account set --name "Terraform Fabric"
az account show
az group show --name "rg-fabric"
az group show --name "rg-terraform"
ll -d ~/git/demo
gh repo view richeney/fabric_demo
az ad app show --id api://$(az account show --query tenantId -otsv)/fabric_terraform_provider
fab api --method get admin/tenantsettings --query "text.tenantSettings[?tenantSettingGroup == 'Developer settings']" | jq .
fab ls -l
fab ls -l .capacities