From 0566b235dcb375f42f7332ac077dd67155fcdc7a Mon Sep 17 00:00:00 2001 From: Ragini-Microsoft Date: Fri, 21 Aug 2026 19:15:15 +0530 Subject: [PATCH] Workflows generated by agent --- .github/workflows/_infra.yml | 142 +++++++++++++ .github/workflows/_infra_tf.yml | 163 +++++++++++++++ .github/workflows/_post-deploy.yml | 263 +++++++++++++++++++++++++ .github/workflows/bicep-ci.yml | 70 +++++++ .github/workflows/bicep-deploy.yml | 38 ++++ .github/workflows/terraform-ci.yml | 53 +++++ .github/workflows/terraform-deploy.yml | 39 ++++ 7 files changed, 768 insertions(+) create mode 100644 .github/workflows/_infra.yml create mode 100644 .github/workflows/_infra_tf.yml create mode 100644 .github/workflows/_post-deploy.yml create mode 100644 .github/workflows/bicep-ci.yml create mode 100644 .github/workflows/bicep-deploy.yml create mode 100644 .github/workflows/terraform-ci.yml create mode 100644 .github/workflows/terraform-deploy.yml diff --git a/.github/workflows/_infra.yml b/.github/workflows/_infra.yml new file mode 100644 index 00000000..b1e58d34 --- /dev/null +++ b/.github/workflows/_infra.yml @@ -0,0 +1,142 @@ +name: _infra + +on: + workflow_call: + inputs: + environment: + description: "Logical stage from the Bicep params folder; selects the params file." + required: true + type: string + gh_environment: + description: "GitHub Environment to bind for Variables + gate (for example -preview or )." + required: true + type: string + command: + description: "plan or apply" + required: true + type: string + template_file: + required: false + type: string + default: infra/main.bicep + parameters_file: + description: "Override; defaults to infra/params/.bicepparam" + required: false + type: string + default: "" + outputs: + deployment_name: + description: "Name of the az deployment created on apply (empty on plan). Consumed by a chained post-deploy workflow to read the deployment outputs." + value: ${{ jobs.infra.outputs.deployment_name }} + +permissions: + id-token: write + contents: read + +jobs: + infra: + runs-on: ubuntu-latest + environment: ${{ inputs.gh_environment }} + outputs: + deployment_name: ${{ steps.deploy.outputs.deployment_name }} + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - uses: azure/login@532459ea530d8321f2fb9bb10d1e0bcf23869a43 # v3.0.0 + with: + client-id: ${{ vars.AZURE_CLIENT_ID }} + tenant-id: ${{ vars.AZURE_TENANT_ID }} + subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }} + + - name: Resolve parameters file + id: params + env: + PARAMETERS_FILE: ${{ inputs.parameters_file }} + TEMPLATE_FILE: ${{ inputs.template_file }} + ENVIRONMENT: ${{ inputs.environment }} + run: | + file="$PARAMETERS_FILE" + if [ -z "$file" ]; then + file="$(dirname "$TEMPLATE_FILE")/params/$ENVIRONMENT.bicepparam" + fi + if [ ! -f "$file" ]; then + echo "::error::Parameters file '$file' not found. This repo needs a per-environment parameters file for '$ENVIRONMENT' (see the skill's naming-conventions.md)." + exit 1 + fi + echo "file=$file" >> "$GITHUB_OUTPUT" + + - name: Resolve target resource group + id: target + env: + PARAMS_FILE: ${{ steps.params.outputs.file }} + run: | + bp="$(az bicep build-params --file "$PARAMS_FILE" --stdout)" + rg="$(echo "$bp" | jq -r '.parametersJson | fromjson | .parameters.resourceGroupName.value // ""')" + if [ -z "$rg" ] || [ "$rg" = "null" ]; then + echo "::error::Parameter 'resourceGroupName' not found in '$PARAMS_FILE'. Add it to the .bicepparam file so Bicep config drives the target resource group (see the skill's naming-conventions.md)." + exit 1 + fi + loc="$(echo "$bp" | jq -r '.parametersJson | fromjson | .parameters.location.value // ""')" + echo "resource_group=$rg" >> "$GITHUB_OUTPUT" + echo "location=$loc" >> "$GITHUB_OUTPUT" + + - name: Ensure resource group exists + if: vars.CREATE_RESOURCE_GROUP != 'false' + env: + RESOURCE_GROUP: ${{ steps.target.outputs.resource_group }} + PARAM_LOCATION: ${{ steps.target.outputs.location }} + VAR_LOCATION: ${{ vars.AZURE_LOCATION }} + run: | + location="$PARAM_LOCATION" + if [ -z "$location" ]; then + location="$VAR_LOCATION" + fi + if [ -z "$location" ]; then + echo "::error::The resource group is created by default but no location is set. Add a 'location' parameter to the .bicepparam file, or define the AZURE_LOCATION Environment Variable (or set CREATE_RESOURCE_GROUP=false if the resource group already exists)." + exit 1 + fi + echo "Ensuring resource group '$RESOURCE_GROUP' exists in '$location' (idempotent)..." + az group create --name "$RESOURCE_GROUP" --location "$location" --only-show-errors --output none + + - name: What-if + if: inputs.command == 'plan' + env: + RESOURCE_GROUP: ${{ steps.target.outputs.resource_group }} + TEMPLATE_FILE: ${{ inputs.template_file }} + PARAMS_FILE: ${{ steps.params.outputs.file }} + run: | + az deployment group what-if \ + --resource-group "$RESOURCE_GROUP" \ + --template-file "$TEMPLATE_FILE" \ + --parameters "$PARAMS_FILE" | tee plan.txt + + - name: Publish what-if to check summary + if: inputs.command == 'plan' && always() + env: + ENVIRONMENT: ${{ inputs.environment }} + run: | + { + echo "### Infra what-if — \`$ENVIRONMENT\`" + echo '' + echo '```' + cat plan.txt 2>/dev/null || echo '(no plan output)' + echo '```' + } >> "$GITHUB_STEP_SUMMARY" + + - name: Deploy + id: deploy + if: inputs.command == 'apply' + env: + RESOURCE_GROUP: ${{ steps.target.outputs.resource_group }} + TEMPLATE_FILE: ${{ inputs.template_file }} + PARAMS_FILE: ${{ steps.params.outputs.file }} + RUN_ID: ${{ github.run_id }} + RUN_ATTEMPT: ${{ github.run_attempt }} + run: | + DEPLOYMENT_NAME="gh-$RUN_ID-$RUN_ATTEMPT" + az deployment group create \ + --name "$DEPLOYMENT_NAME" \ + --resource-group "$RESOURCE_GROUP" \ + --template-file "$TEMPLATE_FILE" \ + --parameters "$PARAMS_FILE" + echo "deployment_name=$DEPLOYMENT_NAME" >> "$GITHUB_OUTPUT" \ No newline at end of file diff --git a/.github/workflows/_infra_tf.yml b/.github/workflows/_infra_tf.yml new file mode 100644 index 00000000..a82f7679 --- /dev/null +++ b/.github/workflows/_infra_tf.yml @@ -0,0 +1,163 @@ +name: _infra_tf + +# Reusable Terraform engine: runs `terraform plan`/`apply` for one environment against the +# repo's existing Terraform under infra_tf. Rendered by the cicd-terraform-workflows +# skill. It never edits the repo's .tf sources — it only writes throwaway backend files +# (backend.tf + backend..hcl, both git-ignored) at runtime so state lives in the +# per-environment Azure Storage backend. Coexists with the Bicep pipeline; nothing is replaced. + +on: + workflow_call: + inputs: + environment: + description: "Logical stage; selects the tfvars file and the state key." + required: true + type: string + gh_environment: + description: "GitHub Environment to bind for Variables + gate (for example -preview or )." + required: true + type: string + command: + description: "plan or apply" + required: true + type: string + working_directory: + required: false + type: string + default: infra_tf + var_file: + description: "Override; defaults to .tfvars inside working_directory." + required: false + type: string + default: "" + terraform_version: + required: false + type: string + default: "1.9.x" + +permissions: + id-token: write + contents: read + +# OIDC for both the azurerm provider AND the azurerm state backend (no client secret). +env: + ARM_USE_OIDC: "true" + ARM_USE_AZUREAD: "true" + ARM_CLIENT_ID: ${{ vars.AZURE_CLIENT_ID }} + ARM_TENANT_ID: ${{ vars.AZURE_TENANT_ID }} + ARM_SUBSCRIPTION_ID: ${{ vars.AZURE_SUBSCRIPTION_ID }} + TF_VAR_subscription_id: ${{ vars.AZURE_SUBSCRIPTION_ID }} + +jobs: + infra: + runs-on: ubuntu-latest + environment: ${{ inputs.gh_environment }} + defaults: + run: + working-directory: ${{ inputs.working_directory }} + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - uses: hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e # v4.0.1 + with: + terraform_version: ${{ inputs.terraform_version }} + terraform_wrapper: false + + - uses: azure/login@532459ea530d8321f2fb9bb10d1e0bcf23869a43 # v3.0.0 + with: + client-id: ${{ vars.AZURE_CLIENT_ID }} + tenant-id: ${{ vars.AZURE_TENANT_ID }} + subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }} + + - name: Resolve var-file + id: vars + env: + VAR_FILE: ${{ inputs.var_file }} + ENVIRONMENT: ${{ inputs.environment }} + run: | + file="$VAR_FILE" + if [ -z "$file" ]; then + file="$ENVIRONMENT.tfvars" + fi + if [ ! -f "$file" ]; then + echo "::error::Terraform var-file '$file' not found in '${{ inputs.working_directory }}'. This repo needs a per-environment tfvars for '$ENVIRONMENT' (see the skill's naming-conventions.md)." + exit 1 + fi + echo "file=$file" >> "$GITHUB_OUTPUT" + + - name: Write backend configuration + env: + BACKEND_RG: ${{ vars.TF_BACKEND_RESOURCE_GROUP }} + BACKEND_SA: ${{ vars.TF_BACKEND_STORAGE_ACCOUNT }} + BACKEND_CT: ${{ vars.TF_BACKEND_CONTAINER }} + ENVIRONMENT: ${{ inputs.environment }} + run: | + missing="" + [ -n "$BACKEND_RG" ] || missing="$missing TF_BACKEND_RESOURCE_GROUP" + [ -n "$BACKEND_SA" ] || missing="$missing TF_BACKEND_STORAGE_ACCOUNT" + [ -n "$BACKEND_CT" ] || missing="$missing TF_BACKEND_CONTAINER" + if [ -n "$missing" ]; then + echo "::error::Missing state-backend Environment Variable(s):$missing. Provision the backend first (see the skill's references/backend-bootstrap.md) and set these as GitHub Environment Variables." + exit 1 + fi + if grep -rqsE 'backend[[:space:]]+"azurerm"' --include='*.tf' .; then + echo "Existing azurerm backend block found in the Terraform sources; not writing backend.tf (its settings are supplied from the .hcl at init)." + else + cat > backend.tf <<'EOF' + terraform { + backend "azurerm" { + use_oidc = true + use_azuread_auth = true + } + } + EOF + fi + cat > "backend.$ENVIRONMENT.hcl" <> "$GITHUB_OUTPUT" ;; + 2) echo "has_changes=true" >> "$GITHUB_OUTPUT" ;; + *) echo "::error::terraform plan exited with status $ec"; exit "$ec" ;; + esac + { + echo "### Terraform plan — \`${{ inputs.environment }}\`" + echo '' + echo '```hcl' + terraform show -no-color tfplan + echo '```' + } >> "$GITHUB_STEP_SUMMARY" + + - name: Upload tfplan + if: inputs.command == 'plan' + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: tfplan-${{ inputs.environment }} + path: ${{ inputs.working_directory }}/tfplan + retention-days: 1 + if-no-files-found: error + + - name: Terraform apply + if: inputs.command == 'apply' + env: + VAR_FILE: ${{ steps.vars.outputs.file }} + run: terraform apply -input=false -auto-approve -var-file="$VAR_FILE" \ No newline at end of file diff --git a/.github/workflows/_post-deploy.yml b/.github/workflows/_post-deploy.yml new file mode 100644 index 00000000..3de6872b --- /dev/null +++ b/.github/workflows/_post-deploy.yml @@ -0,0 +1,263 @@ +name: _post-deploy + +# Reusable, solution-agnostic post-deploy engine. After the infrastructure is provisioned it +# reconstructs the azd environment from the deployment outputs and runs the solution's own +# post-deploy scripts (discovered by the cicd-post-deploy skill from azure.yaml hooks and/or the +# deployment guide). It contains NO solution-specific knowledge: the ordered script list and any +# runtime toggles are substituted at generation time via the placeholders. It never edits +# the repo's Bicep/app/post-provision sources. + +on: + workflow_call: + inputs: + environment: + description: "Logical stage from the infra params folder; selects the params file used to resolve the target resource group." + required: true + type: string + gh_environment: + description: "GitHub Environment to bind for Variables + gate (for example )." + required: true + type: string + azd_env_name: + description: "azd environment name to reconstruct on the runner. Defaults to the logical stage." + required: false + type: string + default: "" + deployment_name: + description: "Infra deployment name to read outputs from. When empty, the latest succeeded deployment in the resource group is used." + required: false + type: string + default: "" + template_file: + required: false + type: string + default: infra/main.bicep + parameters_file: + description: "Override; defaults to infra/params/.bicepparam" + required: false + type: string + default: "" + infra_flavor: + description: "bicep or terraform. Selects how the target resource group and infra outputs are resolved. Defaults to bicep so existing Bicep pipelines are unchanged." + required: false + type: string + default: bicep + working_directory: + description: "Terraform root (used only when infra_flavor == terraform)." + required: false + type: string + default: infra_tf + terraform_version: + description: "Terraform version (used only when infra_flavor == terraform)." + required: false + type: string + default: "1.9.x" + +permissions: + id-token: write + contents: read + +jobs: + post-deploy: + runs-on: ubuntu-latest + environment: ${{ inputs.gh_environment }} + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - uses: azure/login@532459ea530d8321f2fb9bb10d1e0bcf23869a43 # v3.0.0 + with: + client-id: ${{ vars.AZURE_CLIENT_ID }} + tenant-id: ${{ vars.AZURE_TENANT_ID }} + subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }} + + - name: Resolve parameters file + id: params + if: inputs.infra_flavor != 'terraform' + env: + PARAMETERS_FILE: ${{ inputs.parameters_file }} + TEMPLATE_FILE: ${{ inputs.template_file }} + ENVIRONMENT: ${{ inputs.environment }} + run: | + file="$PARAMETERS_FILE" + if [ -z "$file" ]; then + file="$(dirname "$TEMPLATE_FILE")/params/$ENVIRONMENT.bicepparam" + fi + if [ ! -f "$file" ]; then + echo "::error::Parameters file '$file' not found for environment '$ENVIRONMENT'." + exit 1 + fi + echo "file=$file" >> "$GITHUB_OUTPUT" + + - name: Resolve target resource group + id: target + if: inputs.infra_flavor != 'terraform' + env: + PARAMS_FILE: ${{ steps.params.outputs.file }} + run: | + rg="$(az bicep build-params --file "$PARAMS_FILE" --stdout \ + | jq -r '.parametersJson | fromjson | .parameters.resourceGroupName.value // ""')" + if [ -z "$rg" ] || [ "$rg" = "null" ]; then + echo "::error::Parameter 'resourceGroupName' not found in '$PARAMS_FILE'." + exit 1 + fi + echo "resource_group=$rg" >> "$GITHUB_OUTPUT" + + - name: Resolve deployment name + id: deployment + if: inputs.infra_flavor != 'terraform' + env: + RESOURCE_GROUP: ${{ steps.target.outputs.resource_group }} + DEPLOYMENT_NAME: ${{ inputs.deployment_name }} + run: | + name="$DEPLOYMENT_NAME" + if [ -z "$name" ]; then + name="$(az deployment group list \ + --resource-group "$RESOURCE_GROUP" \ + --query "sort_by([?properties.provisioningState=='Succeeded'], &properties.timestamp)[-1].name" \ + -o tsv)" + fi + if [ -z "$name" ] || [ "$name" = "None" ]; then + echo "::error::No succeeded deployment found in resource group '$RESOURCE_GROUP'. Provide 'deployment_name' or run the infra deploy first." + exit 1 + fi + echo "name=$name" >> "$GITHUB_OUTPUT" + + - name: Collect infra outputs (bicep) + id: outputs_bicep + if: inputs.infra_flavor != 'terraform' + env: + RESOURCE_GROUP: ${{ steps.target.outputs.resource_group }} + DEPLOYMENT_NAME: ${{ steps.deployment.outputs.name }} + run: | + az deployment group show \ + --resource-group "$RESOURCE_GROUP" \ + --name "$DEPLOYMENT_NAME" \ + --query properties.outputs -o json > infra-outputs.json + echo "resource_group=$RESOURCE_GROUP" >> "$GITHUB_OUTPUT" + + - name: Set up Terraform + if: inputs.infra_flavor == 'terraform' + uses: hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e # v4.0.1 + with: + terraform_version: ${{ inputs.terraform_version }} + terraform_wrapper: false + + - name: Terraform init (read-only against existing state) + if: inputs.infra_flavor == 'terraform' + working-directory: ${{ inputs.working_directory }} + env: + ARM_USE_OIDC: "true" + ARM_USE_AZUREAD: "true" + ARM_CLIENT_ID: ${{ vars.AZURE_CLIENT_ID }} + ARM_TENANT_ID: ${{ vars.AZURE_TENANT_ID }} + ARM_SUBSCRIPTION_ID: ${{ vars.AZURE_SUBSCRIPTION_ID }} + BACKEND_RG: ${{ vars.TF_BACKEND_RESOURCE_GROUP }} + BACKEND_SA: ${{ vars.TF_BACKEND_STORAGE_ACCOUNT }} + BACKEND_CT: ${{ vars.TF_BACKEND_CONTAINER }} + ENVIRONMENT: ${{ inputs.environment }} + run: | + if grep -rqsE 'backend[[:space:]]+"azurerm"' --include='*.tf' .; then + echo "Existing azurerm backend block found in the Terraform sources; not writing backend.tf." + else + cat > backend.tf <<'EOF' + terraform { + backend "azurerm" { + use_oidc = true + use_azuread_auth = true + } + } + EOF + fi + cat > "backend.$ENVIRONMENT.hcl" < "$GITHUB_WORKSPACE/infra-outputs.json" + echo "resource_group=$rg" >> "$GITHUB_OUTPUT" + + - name: Install azd + run: | + curl -fsSL https://aka.ms/install-azd.sh | bash + azd version + + - name: Hydrate azd environment from infra outputs + env: + AZD_ENV: ${{ inputs.azd_env_name != '' && inputs.azd_env_name || inputs.environment }} + run: | + export AZURE_SUBSCRIPTION_ID="${{ vars.AZURE_SUBSCRIPTION_ID }}" + export AZURE_LOCATION="${{ vars.AZURE_LOCATION }}" + azd env new "$AZD_ENV" --no-prompt \ + --subscription "$AZURE_SUBSCRIPTION_ID" \ + ${AZURE_LOCATION:+--location "$AZURE_LOCATION"} || azd env select "$AZD_ENV" + + jq -r ' + to_entries[] + | select((.value.value | type) as $t | $t=="string" or $t=="number" or $t=="boolean") + | "\(.key | ascii_upcase)\t\(.value.value | tostring)" + ' infra-outputs.json > kv.tsv + + : > .env + while IFS=$'\t' read -r k v; do + [ -n "$k" ] || continue + azd env set "$k" "$v" + printf '%s=%s\n' "$k" "$v" >> .env + printf '%s=%s\n' "$k" "$v" >> "$GITHUB_ENV" + done < kv.tsv + echo "Hydrated $(wc -l < kv.tsv) output(s) into azd env '$AZD_ENV', .env and \$GITHUB_ENV." + + - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 + if: ${{ true }} + with: + python-version: "3.11" + + - name: Install post-deploy Python dependencies + if: ${{ true }} + run: | + python -m pip install uv + uv pip install --system -r infra/scripts/post-provision/requirements.txt + + - name: Run post-deploy steps + shell: bash + env: + AZURE_ENV_NAME: ${{ inputs.azd_env_name != '' && inputs.azd_env_name || inputs.environment }} + run: | + set -euo pipefail + run_step() { + runner="$1"; path="$2"; shift 2 + if [ ! -f "$path" ]; then + echo "::error::post-deploy script '$path' not found."; exit 1 + fi + echo "::group::$runner $path $*" + case "$runner" in + bash) bash "$path" "$@" ;; + pwsh) pwsh -File "$path" "$@" ;; + python) python "$path" "$@" ;; + *) echo "::error::unknown runner '$runner' for '$path'"; exit 1 ;; + esac + echo "::endgroup::" + } + run_step bash infra/scripts/build/build-and-push-acr.sh + printf '\n' | run_step python infra/scripts/post-provision/00_build_solution.py --scenario retail + + - name: Manual post-steps (reminder) + if: always() + run: | + { + echo "### Post-deploy — manual steps not run by CI" + echo '' + echo "- Configure OBO authentication, app registration, API permissions, client secret, and admin consent as documented in documents/SetupOBOAuthentication.md.
- Optionally publish the Fabric Data Agent to Teams through Copilot Studio as documented in documents/CopilotStudioDeployment.md." + } >> "$GITHUB_STEP_SUMMARY" \ No newline at end of file diff --git a/.github/workflows/bicep-ci.yml b/.github/workflows/bicep-ci.yml new file mode 100644 index 00000000..6438070f --- /dev/null +++ b/.github/workflows/bicep-ci.yml @@ -0,0 +1,70 @@ +name: bicep-ci + +on: + pull_request: + branches: [main] + paths: + - "infra/**" + - ".github/workflows/bicep-ci.yml" + - ".github/workflows/_infra.yml" + +permissions: + contents: read + +jobs: + lint-build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - name: Install Bicep CLI + run: az bicep install + + - name: Lint and build + run: | + set -euo pipefail + shopt -s nullglob globstar + found=0 + for t in infra/**/*.bicep; do + found=1 + echo "::group::lint $t"; az bicep lint --file "$t"; echo "::endgroup::" + echo "::group::build $t"; az bicep build --file "$t" --stdout >/dev/null; echo "::endgroup::" + done + for p in infra/**/*.bicepparam; do + echo "::group::build-params $p"; az bicep build-params --file "$p" --stdout >/dev/null; echo "::endgroup::" + done + if [ "$found" -eq 0 ]; then + echo "::error::No .bicep files found under infra/"; exit 1 + fi + + - name: Format check + run: | + set -euo pipefail + shopt -s nullglob globstar + rc=0 + for t in infra/**/*.bicep infra/**/*.bicepparam; do + az bicep format --file "$t" --stdout > /tmp/formatted 2>/dev/null || continue + if ! diff -q "$t" /tmp/formatted >/dev/null; then + echo "::error file=$t::Not formatted. Run: az bicep format --file $t" + diff -u "$t" /tmp/formatted || true + rc=1 + fi + done + exit $rc + + plan: + needs: lint-build + permissions: + id-token: write + contents: read + # What-if for every environment. Each binds its ungated `-preview` environment so a PR + # never waits on required reviewers; auth comes from that environment's own Variables. + strategy: + fail-fast: false + matrix: + environment: [dev] + uses: ./.github/workflows/_infra.yml + with: + environment: ${{ matrix.environment }} + gh_environment: ${{ matrix.environment }}-preview + command: plan \ No newline at end of file diff --git a/.github/workflows/bicep-deploy.yml b/.github/workflows/bicep-deploy.yml new file mode 100644 index 00000000..881a9bdb --- /dev/null +++ b/.github/workflows/bicep-deploy.yml @@ -0,0 +1,38 @@ +name: bicep-deploy + +on: + push: + branches: [main] + workflow_dispatch: + +permissions: + id-token: write + contents: read + +concurrency: + group: bicep-deploy + cancel-in-progress: false + +jobs: + plan-dev: + uses: ./.github/workflows/_infra.yml + with: + environment: dev + gh_environment: dev-preview + command: plan + + apply-dev: + needs: plan-dev + uses: ./.github/workflows/_infra.yml + with: + environment: dev + gh_environment: dev + command: apply + + post-deploy-dev: + needs: apply-dev + uses: ./.github/workflows/_post-deploy.yml + with: + environment: dev + gh_environment: dev + deployment_name: ${{ needs.apply-dev.outputs.deployment_name }} \ No newline at end of file diff --git a/.github/workflows/terraform-ci.yml b/.github/workflows/terraform-ci.yml new file mode 100644 index 00000000..f33f962b --- /dev/null +++ b/.github/workflows/terraform-ci.yml @@ -0,0 +1,53 @@ +name: terraform-ci + +on: + pull_request: + branches: [main] + paths: + - "infra_tf/**" + - ".github/workflows/terraform-ci.yml" + - ".github/workflows/_infra_tf.yml" + +permissions: + contents: read + +jobs: + lint: + runs-on: ubuntu-latest + defaults: + run: + working-directory: infra_tf + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - uses: hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e # v4.0.1 + with: + terraform_version: "1.9.x" + terraform_wrapper: false + + - name: Terraform fmt (check) + run: terraform fmt -check -recursive + + - name: Terraform init (no backend) + # -backend=false validates config + installs providers without touching remote state. + run: terraform init -backend=false -input=false + + - name: Terraform validate + run: terraform validate + + plan: + needs: lint + permissions: + id-token: write + contents: read + # Plan for every environment. Each binds its ungated `-preview` environment so a PR never + # waits on required reviewers; auth + state-backend config come from that environment's Variables. + strategy: + fail-fast: false + matrix: + environment: [dev] + uses: ./.github/workflows/_infra_tf.yml + with: + environment: ${{ matrix.environment }} + gh_environment: ${{ matrix.environment }}-preview + command: plan \ No newline at end of file diff --git a/.github/workflows/terraform-deploy.yml b/.github/workflows/terraform-deploy.yml new file mode 100644 index 00000000..63b745f7 --- /dev/null +++ b/.github/workflows/terraform-deploy.yml @@ -0,0 +1,39 @@ +name: terraform-deploy + +on: + push: + branches: [main] + workflow_dispatch: + +permissions: + id-token: write + contents: read + +concurrency: + group: terraform-deploy + cancel-in-progress: false + +jobs: + plan-dev: + uses: ./.github/workflows/_infra_tf.yml + with: + environment: dev + gh_environment: dev-preview + command: plan + + apply-dev: + needs: plan-dev + uses: ./.github/workflows/_infra_tf.yml + with: + environment: dev + gh_environment: dev + command: apply + + post-deploy-dev: + needs: apply-dev + uses: ./.github/workflows/_post-deploy.yml + with: + environment: dev + gh_environment: dev + infra_flavor: terraform + working_directory: infra_tf \ No newline at end of file