Skip to content

Add sovereign landing zone deployment workflow (validate → what-if → deploy)#7

Merged
Jasazure merged 2 commits intomainfrom
copilot/add-github-actions-workflow
Mar 5, 2026
Merged

Add sovereign landing zone deployment workflow (validate → what-if → deploy)#7
Jasazure merged 2 commits intomainfrom
copilot/add-github-actions-workflow

Conversation

Copy link
Copy Markdown

Copilot AI commented Mar 5, 2026

Three-stage GitHub Actions pipeline for sovereign cloud platform landing zone Bicep deployments, referenced from REUSE_CATALOG.md.

Pipeline stages

  • validateaz bicep lint, az bicep build to dist/sovereign-lz.json, az deployment sub validate, upload compiled artifact
  • what-ifaz deployment sub what-if on compiled artifact, posts results as PR comment; continue-on-error: true (informational, never blocks deploy)
  • deployaz deployment sub create at subscription scope, guarded by environment: sovereign-production and concurrency: { group: sovereign-lz-deploy, cancel-in-progress: false }

Triggers & gating

  • pull_request → main: validate + what-if only
  • push → main: all three stages
  • workflow_dispatch: manual with environment (dev/prod) and dry_run (boolean) inputs; deploy runs only when dry_run == false
  • Deploy uses always() + needs.validate.result == 'success' so what-if failures don't block it

Auth

OIDC federation via azure/login@v2 — no client secrets. Permissions scoped to id-token: write, contents: read, pull-requests: write.

Parameter file selection

# Resolved per-job based on trigger context
if workflow_dispatch → {environment}.parameters.json
elif refs/heads/main → prod.parameters.json
else → dev.parameters.json

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.

…at-if, deploy stages

Co-authored-by: Jasazure <233270744+Jasazure@users.noreply.github.com>
Copilot AI changed the title [WIP] Add GitHub Actions workflow for sovereign landing zone Add sovereign landing zone deployment workflow (validate → what-if → deploy) Mar 5, 2026
@Jasazure Jasazure marked this pull request as ready for review March 5, 2026 12:01
Copilot AI review requested due to automatic review settings March 5, 2026 12:01
@Jasazure Jasazure merged commit eca930b into main Mar 5, 2026
4 of 5 checks passed
@Jasazure Jasazure deleted the copilot/add-github-actions-workflow branch March 5, 2026 12:02
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 validate job to lint/build Bicep and run az deployment sub validate, publishing the compiled ARM template as an artifact.
  • Introduces what-if job to run az deployment sub what-if on the compiled artifact and post results to the PR.
  • Introduces deploy job gated by a protected environment + concurrency, running az deployment sub create on push to main or manual dispatch when dry_run is false.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +211 to +215
(github.event_name == 'workflow_dispatch' && github.event.inputs.dry_run == 'false')
)
runs-on: ubuntu-latest
environment: sovereign-production
concurrency:
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +252 to +258
# - 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
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
# - 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

Copilot uses AI. Check for mistakes.
Comment on lines +173 to +179
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) {
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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) {

Copilot uses AI. Check for mistakes.
Comment on lines +54 to +59
- 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 }}
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +63 to +80
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"
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
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"

Copilot uses AI. Check for mistakes.
Comment on lines +76 to +90
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 }}
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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

Copilot uses AI. Check for mistakes.
Comment on lines +17 to +24
environment:
description: 'Target environment (dev or prod)'
required: true
type: choice
options:
- dev
- prod
default: dev
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants