Skip to content

feat: add x

feat: add x #7

Workflow file for this run

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"