Conversation
…at-if, deploy stages Co-authored-by: Jasazure <233270744+Jasazure@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds a three-stage GitHub Actions workflow intended to validate, preview (what-if), and deploy a “sovereign landing zone” Bicep deployment at subscription scope using OIDC auth, with PR what-if output posted as a comment.
Changes:
- Introduces
validatejob to lint/build Bicep and runaz deployment sub validate, publishing the compiled ARM template as an artifact. - Introduces
what-ifjob to runaz deployment sub what-ifon the compiled artifact and post results to the PR. - Introduces
deployjob gated by a protected environment + concurrency, runningaz deployment sub createon push tomainor manual dispatch whendry_runis false.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| (github.event_name == 'workflow_dispatch' && github.event.inputs.dry_run == 'false') | ||
| ) | ||
| runs-on: ubuntu-latest | ||
| environment: sovereign-production | ||
| concurrency: |
There was a problem hiding this comment.
The deploy job always runs under the sovereign-production protected environment regardless of the workflow_dispatch environment input, and it always logs into the same subscription via AZURE_SUBSCRIPTION_ID. This makes it possible to deploy a “dev” parameter set through the production gate into the production subscription. Consider mapping dev/prod to different GitHub environments and/or subscription IDs, or explicitly disallow environment: dev when running the deploy job.
| # - modules/sovereign/policy/sovereign-policy-initiative.bicep | ||
| # - modules/sovereign/encryption/sovereign-keyvault-cmk.bicep | ||
| # - modules/sovereign/encryption/sovereign-tls-enforcement.bicep | ||
| # - modules/sovereign/confidential-compute/sovereign-confidential-vm.bicep | ||
| # - modules/sovereign/confidential-compute/sovereign-confidential-aks-nodepool.bicep | ||
| # - modules/sovereign/hybrid-arc/sovereign-arc-governance.bicep | ||
| # - modules/sovereign/identity/sovereign-rbac-assignments.bicep |
There was a problem hiding this comment.
The module paths listed in the deploy-step comment don’t match the repository layout. The sovereign modules currently live directly under 03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/modules/ (e.g., sovereign-policy-initiative.bicep), not under modules/sovereign/.... Please update these paths to avoid misleading operators/debugging the wrong files.
| # - modules/sovereign/policy/sovereign-policy-initiative.bicep | |
| # - modules/sovereign/encryption/sovereign-keyvault-cmk.bicep | |
| # - modules/sovereign/encryption/sovereign-tls-enforcement.bicep | |
| # - modules/sovereign/confidential-compute/sovereign-confidential-vm.bicep | |
| # - modules/sovereign/confidential-compute/sovereign-confidential-aks-nodepool.bicep | |
| # - modules/sovereign/hybrid-arc/sovereign-arc-governance.bicep | |
| # - modules/sovereign/identity/sovereign-rbac-assignments.bicep | |
| # - modules/sovereign-policy-initiative.bicep | |
| # - modules/sovereign-keyvault-cmk.bicep | |
| # - modules/sovereign-tls-enforcement.bicep | |
| # - modules/sovereign-confidential-vm.bicep | |
| # - modules/sovereign-confidential-aks-nodepool.bicep | |
| # - modules/sovereign-arc-governance.bicep | |
| # - modules/sovereign-rbac-assignments.bicep |
| const comments = await github.rest.issues.listComments({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: context.issue.number | ||
| }); | ||
|
|
||
| for (const comment of comments.data) { |
There was a problem hiding this comment.
issues.listComments is called without pagination/per_page handling, so it only scans the first page of comments (default 30). On PRs with more comments, older workflow comments may not be found/deleted, resulting in duplicates. Consider requesting a higher per_page and/or paginating until the matching comment is found.
| const comments = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number | |
| }); | |
| for (const comment of comments.data) { | |
| const comments = await github.paginate( | |
| github.rest.issues.listComments, | |
| { | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| per_page: 100 | |
| } | |
| ); | |
| for (const comment of comments) { |
| - name: Log in to Azure with OIDC | ||
| uses: azure/login@v2 | ||
| with: | ||
| client-id: ${{ secrets.AZURE_CLIENT_ID }} | ||
| tenant-id: ${{ secrets.AZURE_TENANT_ID }} | ||
| subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} |
There was a problem hiding this comment.
Because this runs on pull_request, azure/login will fail for PRs coming from forks (GitHub does not pass repository secrets to forked PR workflows). If external contributions are expected, consider adding an if: guard to skip Azure-authenticated steps when github.event.pull_request.head.repo.fork == true, so CI doesn’t hard-fail on forked PRs.
| run: az bicep lint --file deployments/sovereign-baseline/main.bicep | ||
|
|
||
| - name: Compile Bicep to ARM template | ||
| run: | | ||
| mkdir -p dist | ||
| az bicep build \ | ||
| --file deployments/sovereign-baseline/main.bicep \ | ||
| --outfile dist/sovereign-lz.json | ||
|
|
||
| - name: Determine parameter file | ||
| id: params | ||
| run: | | ||
| if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then | ||
| PARAMS_FILE="deployments/sovereign-baseline/${{ github.event.inputs.environment }}.parameters.json" | ||
| elif [[ "${{ github.ref }}" == "refs/heads/main" ]]; then | ||
| PARAMS_FILE="deployments/sovereign-baseline/prod.parameters.json" | ||
| else | ||
| PARAMS_FILE="deployments/sovereign-baseline/dev.parameters.json" |
There was a problem hiding this comment.
The workflow references deployments/sovereign-baseline/main.bicep, but this path doesn’t exist in the repository (and there is no deployments/ directory). As written, az bicep lint/build will fail immediately; update the workflow to point at the actual Bicep entrypoint committed to the repo (or add the missing deployment template in this PR).
| run: az bicep lint --file deployments/sovereign-baseline/main.bicep | |
| - name: Compile Bicep to ARM template | |
| run: | | |
| mkdir -p dist | |
| az bicep build \ | |
| --file deployments/sovereign-baseline/main.bicep \ | |
| --outfile dist/sovereign-lz.json | |
| - name: Determine parameter file | |
| id: params | |
| run: | | |
| if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then | |
| PARAMS_FILE="deployments/sovereign-baseline/${{ github.event.inputs.environment }}.parameters.json" | |
| elif [[ "${{ github.ref }}" == "refs/heads/main" ]]; then | |
| PARAMS_FILE="deployments/sovereign-baseline/prod.parameters.json" | |
| else | |
| PARAMS_FILE="deployments/sovereign-baseline/dev.parameters.json" | |
| run: az bicep lint --file main.bicep | |
| - name: Compile Bicep to ARM template | |
| run: | | |
| mkdir -p dist | |
| az bicep build \ | |
| --file main.bicep \ | |
| --outfile dist/sovereign-lz.json | |
| - name: Determine parameter file | |
| id: params | |
| run: | | |
| if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then | |
| PARAMS_FILE="${{ github.event.inputs.environment }}.parameters.json" | |
| elif [[ "${{ github.ref }}" == "refs/heads/main" ]]; then | |
| PARAMS_FILE="prod.parameters.json" | |
| else | |
| PARAMS_FILE="dev.parameters.json" |
| PARAMS_FILE="deployments/sovereign-baseline/${{ github.event.inputs.environment }}.parameters.json" | ||
| elif [[ "${{ github.ref }}" == "refs/heads/main" ]]; then | ||
| PARAMS_FILE="deployments/sovereign-baseline/prod.parameters.json" | ||
| else | ||
| PARAMS_FILE="deployments/sovereign-baseline/dev.parameters.json" | ||
| fi | ||
| echo "file=$PARAMS_FILE" >> "$GITHUB_OUTPUT" | ||
|
|
||
| - name: Validate deployment at subscription scope | ||
| run: | | ||
| az deployment sub validate \ | ||
| --location "${{ env.DEPLOYMENT_LOCATION }}" \ | ||
| --name "${{ env.DEPLOYMENT_NAME }}" \ | ||
| --template-file dist/sovereign-lz.json \ | ||
| --parameters @${{ steps.params.outputs.file }} |
There was a problem hiding this comment.
The parameter file selection writes paths like deployments/sovereign-baseline/dev.parameters.json / prod.parameters.json, but there are no matching *.parameters.json files in the repo. This will cause az deployment sub validate/what-if/create to fail when it tries to load --parameters @...; either commit these parameter files (in this PR) or adjust the workflow to use the existing parameter file locations.
| PARAMS_FILE="deployments/sovereign-baseline/${{ github.event.inputs.environment }}.parameters.json" | |
| elif [[ "${{ github.ref }}" == "refs/heads/main" ]]; then | |
| PARAMS_FILE="deployments/sovereign-baseline/prod.parameters.json" | |
| else | |
| PARAMS_FILE="deployments/sovereign-baseline/dev.parameters.json" | |
| fi | |
| echo "file=$PARAMS_FILE" >> "$GITHUB_OUTPUT" | |
| - name: Validate deployment at subscription scope | |
| run: | | |
| az deployment sub validate \ | |
| --location "${{ env.DEPLOYMENT_LOCATION }}" \ | |
| --name "${{ env.DEPLOYMENT_NAME }}" \ | |
| --template-file dist/sovereign-lz.json \ | |
| --parameters @${{ steps.params.outputs.file }} | |
| CANDIDATE_PARAMS_FILE="deployments/sovereign-baseline/${{ github.event.inputs.environment }}.parameters.json" | |
| elif [[ "${{ github.ref }}" == "refs/heads/main" ]]; then | |
| CANDIDATE_PARAMS_FILE="deployments/sovereign-baseline/prod.parameters.json" | |
| else | |
| CANDIDATE_PARAMS_FILE="deployments/sovereign-baseline/dev.parameters.json" | |
| fi | |
| if [[ -f "$CANDIDATE_PARAMS_FILE" ]]; then | |
| PARAMS_FILE="$CANDIDATE_PARAMS_FILE" | |
| else | |
| echo "Warning: parameter file '$CANDIDATE_PARAMS_FILE' not found; proceeding without explicit parameters." >&2 | |
| PARAMS_FILE="" | |
| fi | |
| echo "file=$PARAMS_FILE" >> "$GITHUB_OUTPUT" | |
| - name: Validate deployment at subscription scope | |
| run: | | |
| PARAMS_FILE='${{ steps.params.outputs.file }}' | |
| if [[ -n "$PARAMS_FILE" ]]; then | |
| az deployment sub validate \ | |
| --location "${{ env.DEPLOYMENT_LOCATION }}" \ | |
| --name "${{ env.DEPLOYMENT_NAME }}" \ | |
| --template-file dist/sovereign-lz.json \ | |
| --parameters @"$PARAMS_FILE" | |
| else | |
| az deployment sub validate \ | |
| --location "${{ env.DEPLOYMENT_LOCATION }}" \ | |
| --name "${{ env.DEPLOYMENT_NAME }}" \ | |
| --template-file dist/sovereign-lz.json | |
| fi |
| environment: | ||
| description: 'Target environment (dev or prod)' | ||
| required: true | ||
| type: choice | ||
| options: | ||
| - dev | ||
| - prod | ||
| default: dev |
There was a problem hiding this comment.
workflow_dispatch defines an environment input (dev/prod), but this name is easy to confuse with the GitHub Actions job environment that provides deployment protection. If this input is only meant to select a parameters file, consider renaming it (e.g., parameters_profile) to avoid accidental assumptions about where the deployment is going.
Three-stage GitHub Actions pipeline for sovereign cloud platform landing zone Bicep deployments, referenced from REUSE_CATALOG.md.
Pipeline stages
az bicep lint,az bicep buildtodist/sovereign-lz.json,az deployment sub validate, upload compiled artifactaz deployment sub what-ifon compiled artifact, posts results as PR comment;continue-on-error: true(informational, never blocks deploy)az deployment sub createat subscription scope, guarded byenvironment: sovereign-productionandconcurrency: { group: sovereign-lz-deploy, cancel-in-progress: false }Triggers & gating
pull_request → main: validate + what-if onlypush → main: all three stagesworkflow_dispatch: manual withenvironment(dev/prod) anddry_run(boolean) inputs; deploy runs only whendry_run == falsealways()+needs.validate.result == 'success'so what-if failures don't block itAuth
OIDC federation via
azure/login@v2— no client secrets. Permissions scoped toid-token: write,contents: read,pull-requests: write.Parameter file selection
All seven sovereign modules documented as comments on the deploy step. Actions pinned to tagged releases.
💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.