feat: add x #7
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: pipeline | |
| # Trunk-based: `main` is the only long-lived branch. CI runs on every push and | |
| # pull request against it; CD (tag + GitHub Release) runs only on push to main. | |
| # | |
| # Versions come from GitVersion.yaml, which derives them from the Conventional | |
| # Commit types since the last tag — the commit type you write is what decides | |
| # the version consumers pin to. | |
| # | |
| # No cloud credentials are required anywhere in this pipeline: `terraform | |
| # validate` with `-backend=false` resolves providers from the registry and | |
| # checks configuration statically. Nothing here authenticates to Azure, Entra, | |
| # a FortiGate, or the GitHub API beyond the release step. | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| workflow_dispatch: | |
| inputs: | |
| release_version: | |
| description: "Explicit version to release (e.g. 1.4.0). Leave empty to derive it from GitVersion." | |
| required: false | |
| type: string | |
| dry_run: | |
| description: "Resolve the next version and stop without tagging." | |
| required: false | |
| default: true | |
| type: boolean | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: pipeline-${{ github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| env: | |
| # Matches the `required_version = ">= 1.15"` floor every module declares, so | |
| # CI proves the floor is honest rather than testing some newer version. | |
| TERRAFORM_VERSION: "1.15.8" | |
| TF_IN_AUTOMATION: "1" | |
| TF_INPUT: "0" | |
| jobs: | |
| format: | |
| name: format | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: hashicorp/setup-terraform@v3 | |
| with: | |
| terraform_version: ${{ env.TERRAFORM_VERSION }} | |
| terraform_wrapper: false | |
| - name: terraform fmt | |
| run: terraform fmt -recursive -check -diff | |
| docs: | |
| name: docs | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Check documentation consistency | |
| run: python3 .github/scripts/check-docs.py | |
| validate: | |
| name: validate (${{ matrix.family }}) | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| family: [azure, entra, fortios, github] | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: hashicorp/setup-terraform@v3 | |
| with: | |
| terraform_version: ${{ env.TERRAFORM_VERSION }} | |
| terraform_wrapper: false | |
| # Lock files are deliberately not committed, so every `init` would | |
| # otherwise re-download the same providers once per module directory. | |
| - name: Restore provider cache | |
| uses: actions/cache@v5 | |
| with: | |
| path: ~/.terraform.d/plugin-cache | |
| key: tf-plugins-${{ matrix.family }}-${{ hashFiles('src/modules/**/versions.tf') }} | |
| restore-keys: tf-plugins-${{ matrix.family }}- | |
| - name: init and validate | |
| run: | | |
| set -uo pipefail | |
| # Exported here rather than in `env:` — Terraform does not expand a | |
| # leading `~` in TF_PLUGIN_CACHE_DIR, and `env:` values get no shell | |
| # expansion, so the path has to be built at runtime. | |
| export TF_PLUGIN_CACHE_DIR="$HOME/.terraform.d/plugin-cache" | |
| mkdir -p "$TF_PLUGIN_CACHE_DIR" | |
| # Every directory under this family holding .tf files: the module | |
| # itself, any nested submodules, and any examples/. | |
| mapfile -t dirs < <( | |
| find src/modules -path "src/modules/${{ matrix.family }}-*" \ | |
| -name '*.tf' -not -path '*/.terraform/*' -printf '%h\n' \ | |
| | sort -u | |
| ) | |
| echo "Validating ${#dirs[@]} directories in the ${{ matrix.family }} family." | |
| failed=() | |
| for dir in "${dirs[@]}"; do | |
| if ! output=$(terraform -chdir="$dir" init -backend=false -no-color 2>&1); then | |
| echo "::error file=$dir/versions.tf::terraform init failed" | |
| echo "$output" | |
| failed+=("$dir (init)") | |
| continue | |
| fi | |
| if ! output=$(terraform -chdir="$dir" validate -no-color 2>&1); then | |
| echo "::error file=$dir/main.tf::terraform validate failed" | |
| echo "$output" | |
| failed+=("$dir (validate)") | |
| fi | |
| done | |
| { | |
| echo "### validate — ${{ matrix.family }}" | |
| echo | |
| echo "\`${#dirs[@]}\` directories checked, \`${#failed[@]}\` failed." | |
| if [ ${#failed[@]} -gt 0 ]; then | |
| echo | |
| for entry in "${failed[@]}"; do echo "- \`$entry\`"; done | |
| fi | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| [ ${#failed[@]} -eq 0 ] | |
| # Single stable status check for branch protection. Without it the ruleset has | |
| # to name every `validate (<family>)` leg individually, and adding a provider | |
| # family later would silently stop being required. | |
| check_ci: | |
| name: check_ci | |
| needs: [format, docs, validate] | |
| if: always() | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Check results | |
| run: | | |
| if [[ "${{ contains(needs.*.result, 'failure') }}" == "true" ]]; then | |
| echo "::error::One or more pipeline jobs failed" | |
| exit 1 | |
| fi | |
| if [[ "${{ contains(needs.*.result, 'cancelled') }}" == "true" ]]; then | |
| echo "::error::One or more pipeline jobs were cancelled" | |
| exit 1 | |
| fi | |
| echo "All pipeline jobs passed." | |
| release: | |
| name: release | |
| needs: [format, docs, validate] | |
| if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| # Full history and tags — the version is derived from both. | |
| fetch-depth: 0 | |
| - name: gitversion - install | |
| uses: gittools/actions/gitversion/setup@v4.5.0 | |
| with: | |
| versionSpec: 6.x | |
| preferLatestVersion: true | |
| - name: gitversion - execute | |
| id: gitversion | |
| uses: gittools/actions/gitversion/execute@v4.5.0 | |
| with: | |
| configFilePath: GitVersion.yaml | |
| - name: Resolve next version | |
| id: version | |
| env: | |
| EXPLICIT: ${{ inputs.release_version }} | |
| DERIVED: ${{ steps.gitversion.outputs.GitVersion_MajorMinorPatch }} | |
| run: | | |
| set -euo pipefail | |
| if [ -n "$EXPLICIT" ]; then | |
| tag="v${EXPLICIT#v}" | |
| reason="explicit input" | |
| else | |
| tag="v${DERIVED}" | |
| reason="derived by GitVersion from Conventional Commits" | |
| fi | |
| echo "### release" >> "$GITHUB_STEP_SUMMARY" | |
| # Check the remote, not the local clone — another run may have tagged | |
| # between this checkout and now. | |
| existing=$(git ls-remote --tags origin "refs/tags/${tag}" | awk '{print $1}') | |
| if [ -n "$existing" ]; then | |
| # Two very different situations land here, and both mean "do not | |
| # release". GitVersion returns the *current* version when nothing | |
| # releasable has landed, so a docs-only merge resolves to the tag | |
| # that already exists rather than to a new one. | |
| if [ "$existing" = "$GITHUB_SHA" ]; then | |
| echo "\`${tag}\` already tagged at this commit — already released." \ | |
| >> "$GITHUB_STEP_SUMMARY" | |
| else | |
| echo "No releasable commits since \`${tag}\` — docs, chore, ci and test commits do not bump the version." \ | |
| >> "$GITHUB_STEP_SUMMARY" | |
| fi | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| echo "skip=false" >> "$GITHUB_OUTPUT" | |
| echo "tag=${tag}" >> "$GITHUB_OUTPUT" | |
| echo "Next version: \`${tag}\` (${reason})." >> "$GITHUB_STEP_SUMMARY" | |
| # On a push to main the `inputs` context is empty, so dry_run is null and | |
| # the release goes out. A manual run defaults dry_run to true and only | |
| # previews — that asymmetry is deliberate. | |
| - name: Dry run | |
| if: steps.version.outputs.skip == 'false' && inputs.dry_run | |
| run: | | |
| echo "Dry run — would create ${{ steps.version.outputs.tag }}." \ | |
| >> "$GITHUB_STEP_SUMMARY" | |
| - name: Create tag and release | |
| if: steps.version.outputs.skip == 'false' && inputs.dry_run != true | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| TAG: ${{ steps.version.outputs.tag }} | |
| run: | | |
| set -euo pipefail | |
| gh release create "$TAG" \ | |
| --target "$GITHUB_SHA" \ | |
| --title "$TAG" \ | |
| --generate-notes | |
| echo "Released [\`$TAG\`](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/releases/tag/${TAG})." \ | |
| >> "$GITHUB_STEP_SUMMARY" |