A personal repository that acts as a persistent registry and CI orchestrator for Azure Verified Module (AVM) contributions in flight.
External contributors cannot trigger CI in the Azure org — the AVM guidance recommends running checks locally, but that makes evidencing the work harder. This repository re-hosts the same checks as GitHub Actions workflows so external contributors can run them against multiple modules and share publicly-visible CI results with the AVM team.
The avm-contributor-agent can also
trigger these workflows directly via repository_dispatch — no manual YAML editing required.
modules.yamllists every module (and branch) you are working on.- The top-level workflows read that registry and fan out one job per module.
- The E2E workflow then hands each module to a reusable workflow that fans out one job per example.
- Alternatively, any workflow can be triggered via
repository_dispatchfrom an agent — bypassingmodules.yamlentirely.
| Workflow | Trigger | Azure credentials? | What it runs |
|---|---|---|---|
| Checks | push · dispatch | ✓ | make pre-commit → make pr-check (recent AVM modules also run Well-Architected/conftest checks here) |
| E2E tests | workflow_dispatch · repository_dispatch | ✓ | module-level fan-out in this repo, then one example job per discovered example |
| Terraform tests | dispatch · push | unit: ✗ / integration: ✓ | make tf-test-unit / make tf-test-integration |
All workflows accept an optional module_filter
workflow_dispatch input so you can target a single module without editing modules.yaml.
- Docker or Podman — used by the
./avmhelper and pulled automatically by the workflows. - Azure CLI (
az) — for localaz loginbefore running e2e tests.
Three workflows deploy real Azure resources and are gated behind a GitHub Actions
environment named test.
| Workflow | Environment | Azure secrets needed |
|---|---|---|
checks.yml |
test |
ARM_* (pr-check runs terraform plan) |
e2e-tests.yml |
test |
ARM_* |
terraform-tests.yml (integration job) |
test |
ARM_* |
upgrade-tests.yml |
test |
ARM_* |
Create the test environment in Settings → Environments and optionally add protection
rules (e.g. required reviewers) before any Azure credentials are used.
The test environment does not need its own secrets — the workflows read from
repository-level secrets (Settings → Secrets and variables → Actions):
| Secret | Required | Description |
|---|---|---|
ARM_TENANT_ID |
✓ | Microsoft Entra ID tenant ID |
ARM_SUBSCRIPTION_ID |
✓ | Azure subscription to deploy into |
ARM_CLIENT_ID |
✓ | Client ID of the User-Assigned Managed Identity |
AGENT_DISPATCH_TOKEN |
✓ | PAT with contents: write on avm-contributor-agent (for CI callbacks) |
ARM_TENANT_ID_OVERRIDE |
optional | Alternative tenant for modules that need it |
ARM_SUBSCRIPTION_ID_OVERRIDE |
optional | Alternative subscription |
ARM_CLIENT_ID_OVERRIDE |
optional | Alternative managed identity |
Run scripts/setup-azure-oidc.sh to:
- Create a User-Assigned Managed Identity in Azure.
- Assign it
Contributor+Role Based Access Control Administratoron the subscription. - Add a federated credential scoped to the
testenvironment in this repo. - Create the
testGitHub environment. - Set all required GitHub repository secrets.
# Prerequisites: az CLI logged in, gh CLI authenticated
./scripts/setup-azure-oidc.shSee the script for optional environment variables to override the defaults (resource group name, location, identity name, etc.).
Edit modules.yaml and add an entry:
modules:
- source: <github-owner>/<repo-name> # required
branch: <branch-name> # optional – omit to use the default branch
name: <friendly-name> # optional – defaults to the repo nameExample:
modules:
- source: kewalaka/terraform-azurerm-avm-res-app-managedenvironment
branch: kewalaka/fold-tfmodmake-into-module
- source: kewalaka/terraform-azurerm-avm-res-storage-storageaccount
branch: feature/add-lifecycle-policy
name: storage-accountCommit and push — every workflow will automatically pick up the new entry on its next run.
- Navigate to Actions in this repository.
- Select the workflow you want to run.
- Click Run workflow.
- Optionally fill in the
module_filterfield to run only one module (substring match on thesourcefield, e.g.managedenvironment).
# Run checks (pre-commit + pr-check) for all modules
gh workflow run checks.yml
# Run e2e tests for a specific module
gh workflow run e2e-tests.yml -f module_filter=managedenvironmentAny caller with a repo scoped PAT (or contents: write from another Actions workflow)
can trigger CI without touching modules.yaml:
# Run checks on a specific branch (with agent callback)
gh api repos/kewalaka/avm-contributions/dispatches \
--method POST \
--field event_type=module-checks \
--field 'client_payload[source]=kewalaka/terraform-azurerm-avm-res-foo-bar' \
--field 'client_payload[branch]=feature/my-fix' \
--field 'client_payload[callback_repo]=kewalaka/avm-contributor-agent'
# Run terraform unit tests only
gh api repos/kewalaka/avm-contributions/dispatches \
--method POST \
--field event_type=module-tf-test \
--field 'client_payload[source]=kewalaka/terraform-azurerm-avm-res-foo-bar' \
--field 'client_payload[branch]=feature/my-fix' \
--field 'client_payload[test_type]=unit' \
--field 'client_payload[callback_repo]=kewalaka/avm-contributor-agent'
# Trigger e2e tests (requires "test" environment approval)
gh api repos/kewalaka/avm-contributions/dispatches \
--method POST \
--field event_type=module-e2e \
--field 'client_payload[source]=kewalaka/terraform-azurerm-avm-res-foo-bar' \
--field 'client_payload[branch]=feature/my-fix' \
--field 'client_payload[callback_repo]=kewalaka/avm-contributor-agent'
# Trigger upgrade tests
gh api repos/kewalaka/avm-contributions/dispatches \
-X POST \
-f 'event_type=module-upgrade' \
-f 'client_payload[dispatch_id]=manual-001' \
-f 'client_payload[upstream_repo]=Azure/terraform-azurerm-avm-res-app-managedenvironment' \
-f 'client_payload[fork_repo]=kewalaka/terraform-azurerm-avm-res-app-managedenvironment' \
-f 'client_payload[base_ref]=main' \
-f 'client_payload[head_ref]=kewalaka/fold-tfmodmake-into-module' \
-f 'client_payload[example]=default' gh api --field client_payload='{"...":"..."}' sends client_payload as a JSON
string, not an object. Use nested client_payload[...] fields as shown above.
Dispatch event types:
event_type |
Workflow | Payload fields |
|---|---|---|
module-checks |
checks.yml |
source (required), branch (optional), callback_repo (optional) |
module-e2e |
e2e-tests.yml |
source (required), branch (optional), callback_repo (optional) |
module-tf-test |
terraform-tests.yml |
source (required), branch (optional), test_type (optional: both/unit/integration), callback_repo (optional) |
callback_repo — when set to "owner/repo", the workflow fires a repository_dispatch
event of type ci-result back to that repository when the job completes (success or
failure). The target repo must have an AGENT_DISPATCH_TOKEN secret configured in
kewalaka/avm-contributions (Settings → Secrets) with contents: write permission on the
callback repo. The ci-result payload contains:
{
"status": "success | failure",
"module": "kewalaka/terraform-azurerm-avm-res-foo-bar",
"branch": "feature/my-fix",
"workflow": "checks | e2e | unit-tests | integration-tests",
"run_url": "https://github.com/kewalaka/avm-contributions/actions/runs/..."
}For module-e2e and module-tf-test, the source field must belong to an allowed
GitHub org (kewalaka or Azure). The test environment gate provides a second layer
of protection before any Azure credentials are used.
These are the same steps the workflows automate. Run them from inside the cloned module directory.
./avm pre-commit
# If files changed:
git add -A && git commit -m "chore: pre-commit fixes" && git push./avm pr-checkaz login
./avm test-examples./avm tf-test-unitaz login
./avm tf-test-integrationavm-contributions/
├── README.md # this file
├── modules.yaml # registry of in-progress modules
├── scripts/
│ └── setup-azure-oidc.sh # one-shot Azure + GitHub setup
└── .github/
└── workflows/
├── checks.yml # pre-commit + pr-check (replaces pre-commit.yml + pr-check.yml)
├── e2e-tests.yml # entrypoint: builds the module matrix and calls the reusable E2E workflow
├── e2e-module.yml # reusable workflow: discovers examples and fans out one job per example
├── terraform-tests.yml # runs unit + integration terraform tests
└── upgrade-tests.yml # tests module upgrades (apply base → upgrade → verify)
apply_base (BASE module + BASE example config) → create real resources
↓
copy state to head workspace
↓
init_upgrade (HEAD module)
↓
plan_A (HEAD module + BASE example config) ← "does upgrading the module break existing configs?"
│ THIS is the breaking change signal
│
├─ destroys or replacements present? → BREAKING CHANGE ✗
│
└─ neither? → NO BREAKING CHANGE ✓
↓
plan_B (HEAD module + HEAD example config) ← "does the new example also work?"
│
└─ optional apply_head if B is clean
↓
destroy HEAD module if apply_head was attempted (captures partial-apply state)
BASE module if apply_head was skipped (avoids HEAD validation risk)