An Azure platform for running a service on AKS with Terraform, Argo CD, CI, observability and incident handling.
View the Project on GitHub Franklindot04/production-platform-Azure
The GitHub Actions workflows of the repository. Every action is pinned to a full commit SHA with its version in a comment. No workflow uses a stored client secret. The plan workflow uses secrets that hold identifiers only. Each one sets the permissions it needs and nothing more.
| Workflow | File | Runs on | Permissions |
|---|---|---|---|
| Terraform checks | terraform-checks.yml |
Pull requests that change infrastructure/ |
contents: read |
| Terraform plan | terraform-plan.yml |
Pull requests that change infrastructure/ or .github/workflows/terraform-plan.yml |
None at the top, and contents: read and id-token: write in the one job |
| Service, chart and manifest checks | app-checks.yml |
Pull requests that change applications/, helm/, kubernetes/, gitops/ or tests/kind/ |
contents: read |
| Security scans | security-scans.yml |
Pull requests that change infrastructure/, kubernetes/, helm/, gitops/, applications/ or .trivyignore.yaml |
contents: read |
| Publish image | image-publish.yml |
Pushes to main that change applications/sample-service/, and manual runs |
contents: read, and packages: write in the one job that pushes |
| Open pull request for the image tag | image-tag-bump.yml |
When Publish image succeeds on main |
None at the top, and contents: write and pull-requests: write in the one job |
Runs terraform plan -lock=false for the two dev roots (ADR 0022) on pull requests from this repository. It logs in with the plan identity through OIDC, passes the owner ID and the repository ID from the GitHub context to the persistent plan, and needs the four repository secrets listed under “Repository settings the workflows need”. If AZURE_CLIENT_ID is empty or not set, the plan is skipped with a notice.
AZURE_CLIENT_ID is not set, the first step prints a notice and every other step is skipped.azure/login and an OIDC token. No client secret is used.-refresh=false. It sets TF_VAR_alert_email to a placeholder address, so the plan shows a change to the action group receiver. The session plan sets TF_VAR_api_authorized_ip_ranges to a documentation range and TF_VAR_cluster_admin_group_object_id to a placeholder GUID.Two jobs, both running tests/kind/check.sh:
static runs go vet, go test, gofmt, helm lint, kubeconform and yamllint if it is installed. Helm is installed with a pinned version. kubeconform is installed with go install at a pinned version.kind runs after static and runs the whole script, which creates a kind cluster, installs the chart and the cluster rules, and checks them. kind is installed with go install at a pinned version.The checks are described in tests/kind/README.md.
Two jobs run Trivy (ADR 0020).
config scans the repository for misconfiguration in the Terraform, the Kubernetes manifests and the chart, and then scans the files for secrets. The chart is rendered with the dev values, because it needs image.tag.image builds applications/sample-service and scans the image for vulnerabilities and secrets..trivyignore.yaml with a reason.contents: read.Builds applications/sample-service for linux/amd64, which the AKS nodes use, and pushes it to ghcr.io/<owner>/sample-service with the full commit SHA as the only tag (ADR 0013). It logs in with the built-in token. The image carries the labels set by the metadata action.
After Publish image succeeds on main, this workflow checks out main and sets the tag in gitops/dev/values/sample-service.yaml to the commit SHA with .github/scripts/set-image-tag.sh. .github/scripts/open-image-tag-pr.sh then commits it to a branch named chore/sample-service-image-<short SHA> and opens a pull request (ADR 0018). It never pushes to main.
The branch is named after the SHA, so it can exist already:
The pull request is opened in each case. If one is already open for the branch, that one is used. Open pull requests from earlier branches of that name are closed.
A pull request created with the built-in token does not start other workflows, so the checks do not run on it.
These are settings in GitHub. A workflow cannot change them.
AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_SUBSCRIPTION_ID and TFSTATE_ACCOUNT_NAME. They are set after the persistent root is applied. Without AZURE_CLIENT_ID the workflow prints a notice and skips its steps.actionlint
./tests/kind/check.sh --static
.github/scripts/set-image-tag.sh gitops/dev/values/sample-service.yaml <40 character SHA>
The last command changes the file. Reset it with git checkout gitops/dev/values/sample-service.yaml.