From 93066a7c7839fbac41c409ccd33f41c11f635543 Mon Sep 17 00:00:00 2001 From: Nicholas Santiago Date: Sat, 21 Mar 2026 00:28:12 -0400 Subject: [PATCH 1/5] feat: add reusable OpenTofu module workflows and actionlint CI Made-with: Cursor --- .github/workflows/opentofu-module-ci.yml | 158 ++++++++++++++++++ .../release-please-terraform-module.yml | 95 +++++++++++ .github/workflows/semantic-pr-title.yml | 66 ++++++++ .github/workflows/validate-workflows.yml | 24 +++ .gitignore | 1 + README.md | 43 +++++ 6 files changed, 387 insertions(+) create mode 100644 .github/workflows/opentofu-module-ci.yml create mode 100644 .github/workflows/release-please-terraform-module.yml create mode 100644 .github/workflows/semantic-pr-title.yml create mode 100644 .github/workflows/validate-workflows.yml create mode 100644 .gitignore create mode 100644 README.md diff --git a/.github/workflows/opentofu-module-ci.yml b/.github/workflows/opentofu-module-ci.yml new file mode 100644 index 0000000..046eefc --- /dev/null +++ b/.github/workflows/opentofu-module-ci.yml @@ -0,0 +1,158 @@ +--- +name: OpenTofu module CI (reusable) + +on: # yamllint disable-line rule:truthy + workflow_call: + inputs: + tofu_version_file: + description: Path to OpenTofu version file in the caller repository + type: string + default: .opentofu-version + terraform_docs_version: + description: terraform-docs GitHub release tag to install + type: string + default: v0.21.0 + +permissions: + contents: read + +jobs: + pre-commit: + name: Pre-commit + runs-on: ubuntu-latest + env: + TOFU_VERSION_FILE: ${{ inputs.tofu_version_file }} + steps: + - name: Checkout repository + uses: actions/checkout@v6 + - name: Setup Python + uses: actions/setup-python@v6 + - name: Setup TFLint + uses: terraform-linters/setup-tflint@v6 + - name: Setup Terraform-Docs + uses: jaxxstorm/action-install-gh-release@v2.1.0 + with: + repo: terraform-docs/terraform-docs + tag: ${{ inputs.terraform_docs_version }} + - name: Cache pre-commit + uses: actions/cache@v5 + with: + path: ~/.cache/pre-commit + key: pre-commit-${{ runner.os }}-${{ hashFiles('.pre-commit-config.yaml') }} + restore-keys: | + pre-commit-${{ runner.os }}- + - name: Install pre-commit + run: pip install pre-commit + - name: Setup OpenTofu + uses: opentofu/setup-opentofu@v2 + with: + tofu_version_file: ${{ inputs.tofu_version_file }} + tofu_wrapper: false + - name: Run pre-commit + id: pre_commit + shell: bash + run: | + set -euo pipefail + mkdir -p "${RUNNER_TEMP}/ci-summary" + set -o pipefail + set +e + pre-commit run --all-files 2>&1 | tee "${RUNNER_TEMP}/ci-summary/pre-commit.log" + ec=${PIPESTATUS[0]} + set -e + echo "${ec}" > "${RUNNER_TEMP}/ci-summary/pre-commit.exit" + exit "${ec}" + - name: Job summary + if: always() + shell: bash + env: + TOFU_VERSION_FILE: ${{ inputs.tofu_version_file }} + run: | + set -euo pipefail + SUMMARY_DIR="${RUNNER_TEMP}/ci-summary" + EXIT_FILE="${SUMMARY_DIR}/pre-commit.exit" + LOG_FILE="${SUMMARY_DIR}/pre-commit.log" + if [[ -f "${EXIT_FILE}" ]]; then + PRE_COMMIT_EXIT="$(tr -d ' \n\r' < "${EXIT_FILE}")" + else + PRE_COMMIT_EXIT="" + fi + + { + RUN_URL="${{ github.server_url }}/${{ github.repository }}" + RUN_URL="${RUN_URL}/actions/runs/${{ github.run_id }}" + echo "## πŸ”§ OpenTofu CI: Pre-commit" + echo + echo "### πŸ“‹ Run context" + echo + echo "| | |" + echo "|--|--|" + echo "| Event | \`${{ github.event_name }}\` |" + echo "| Ref | \`${{ github.ref }}\` |" + echo "| SHA | \`${{ github.sha }}\` |" + echo "| Workflow run | [${{ github.run_id }}](${RUN_URL}) |" + echo + echo "### πŸ› οΈ Toolchain (this runner)" + echo + echo "| Tool | Version / source |" + echo "|------|------------------|" + if command -v tofu >/dev/null 2>&1; then + printf '| OpenTofu | `%s` |\n' "$(tofu version 2>/dev/null | head -n1 | tr -d '\r')" + fi + if command -v python3 >/dev/null 2>&1; then + printf '| Python | `%s` |\n' "$(python3 --version 2>&1 | tr -d '\r')" + fi + if command -v pre-commit >/dev/null 2>&1; then + printf '| pre-commit | `%s` |\n' "$(pre-commit --version 2>&1 | tr -d '\r')" + fi + if command -v terraform-docs >/dev/null 2>&1; then + printf '| terraform-docs | `%s` |\n' "$(terraform-docs version 2>&1 | tr -d '\r' | head -n1)" + fi + if command -v tflint >/dev/null 2>&1; then + printf '| tflint | `%s` |\n' "$(tflint --version 2>&1 | tr -d '\r' | head -n1)" + fi + if [[ -f "${TOFU_VERSION_FILE}" ]]; then + printf '| Pinned OpenTofu (file `%s`) | `%s` |\n' "${TOFU_VERSION_FILE}" "$(tr -d '\n\r' < "${TOFU_VERSION_FILE}")" + fi + echo + echo "### πŸ§ͺ Pre-commit result" + echo + if [[ "${PRE_COMMIT_EXIT}" == "0" ]]; then + echo "Status: **βœ… passed** (exit 0)." + elif [[ -n "${PRE_COMMIT_EXIT}" ]]; then + echo "Status: **❌ failed** (exit ${PRE_COMMIT_EXIT})." + else + echo "Status: **❓ unknown** (no exit file β€” see Run pre-commit step log)." + fi + echo + echo "Command: \`pre-commit run --all-files\` β€” hooks from " + echo "[\`.pre-commit-config.yaml\`](.pre-commit-config.yaml) (\`commit-msg\` not run in CI)." + echo + if [[ -f "${LOG_FILE}" ]]; then + echo "
" + echo "πŸ“œ Full pre-commit output" + echo + echo '```text' + # Cap size so the summary stays well under GitHub limits (~1 MiB) + if [[ "$(wc -c < "${LOG_FILE}")" -gt 61440 ]]; then + head -n 400 "${LOG_FILE}" + echo "" + echo "… truncated (first 400 lines; full output is in the Run pre-commit step log)" + else + cat "${LOG_FILE}" + fi + echo '```' + echo + echo "
" + else + echo "_No pre-commit log file was written (see the Run pre-commit step)._" + fi + echo + echo "### πŸ’» If checks failed locally" + echo + echo '```bash' + echo "pip install pre-commit" + echo "pre-commit install" + echo "pre-commit install --hook-type commit-msg" + echo "pre-commit run --all-files" + echo '```' + } >> "${GITHUB_STEP_SUMMARY}" diff --git a/.github/workflows/release-please-terraform-module.yml b/.github/workflows/release-please-terraform-module.yml new file mode 100644 index 0000000..ada576f --- /dev/null +++ b/.github/workflows/release-please-terraform-module.yml @@ -0,0 +1,95 @@ +--- +name: Release Please β€” Terraform module (reusable) + +on: # yamllint disable-line rule:truthy + workflow_call: + inputs: + config_file: + description: Release Please config JSON path in the caller repository + type: string + default: release-please-config.json + manifest_file: + description: Release Please manifest JSON path in the caller repository + type: string + default: .release-please-manifest.json + secrets: + RELEASE_PLEASE_TOKEN: + description: PAT with repo contents/PR scope (see module TEMPLATE.md) + required: true + +permissions: + contents: write + issues: write + pull-requests: write + +jobs: + release-please: + name: Release Please + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Release Please + id: release_please + uses: googleapis/release-please-action@v4 + with: + token: ${{ secrets.RELEASE_PLEASE_TOKEN }} + config-file: ${{ inputs.config_file }} + manifest-file: ${{ inputs.manifest_file }} + + - name: Job summary + if: always() + env: + RELEASE_BODY: ${{ steps.release_please.outputs.body }} + run: | + { + RUN_URL="${{ github.server_url }}/${{ github.repository }}" + RUN_URL="${RUN_URL}/actions/runs/${{ github.run_id }}" + echo "## πŸš€ Release Please" + echo + echo "### πŸ“‹ Run context" + echo + echo "| | |" + echo "|--|--|" + echo "| Ref | \`${{ github.ref }}\` |" + echo "| SHA | \`${{ github.sha }}\` |" + echo "| Workflow run | [${{ github.run_id }}](${RUN_URL}) |" + echo "| Config | [${{ inputs.config_file }}](${{ inputs.config_file }}) |" + echo "| Manifest | [${{ inputs.manifest_file }}](${{ inputs.manifest_file }}) |" + echo + echo "### πŸ“€ Action outputs (root component)" + echo + echo "| Output | Value |" + echo "|--------|-------|" + echo "| \`releases_created\` | \`${{ steps.release_please.outputs.releases_created }}\` |" + echo "| \`prs_created\` | \`${{ steps.release_please.outputs.prs_created }}\` |" + echo "| \`release_created\` | \`${{ steps.release_please.outputs.release_created }}\` |" + echo "| \`paths_released\` | \`${{ steps.release_please.outputs.paths_released }}\` |" + echo "| \`version\` | \`${{ steps.release_please.outputs.version }}\` |" + echo "| \`tag_name\` | \`${{ steps.release_please.outputs.tag_name }}\` |" + echo "| \`sha\` | \`${{ steps.release_please.outputs.sha }}\` |" + echo "| \`html_url\` | \`${{ steps.release_please.outputs.html_url }}\` |" + maj="${{ steps.release_please.outputs.major }}" + min="${{ steps.release_please.outputs.minor }}" + pat="${{ steps.release_please.outputs.patch }}" + echo "| \`major\` / \`minor\` / \`patch\` | \`${maj}\` / \`${min}\` / \`${pat}\` |" + echo + echo "### πŸ’‘ Notes" + echo + echo "- Set \`RELEASE_PLEASE_TOKEN\` (PAT). See your module repo’s \`TEMPLATE.md\`." + echo "- When \`prs_created\` is \`true\`, open or refresh the **release PR** and merge to publish." + echo "- \`release_created\` **true**: GitHub Release published β€” see \`html_url\`, \`tag_name\`." + echo + } >> "$GITHUB_STEP_SUMMARY" + + if [[ -n "${RELEASE_BODY:-}" ]]; then + { + echo "### πŸ“ Release notes preview (\`body\` output)" + echo + echo '```markdown' + printf '%s\n' "${RELEASE_BODY}" + echo '```' + } >> "$GITHUB_STEP_SUMMARY" + fi diff --git a/.github/workflows/semantic-pr-title.yml b/.github/workflows/semantic-pr-title.yml new file mode 100644 index 0000000..2fed80f --- /dev/null +++ b/.github/workflows/semantic-pr-title.yml @@ -0,0 +1,66 @@ +--- +name: Semantic PR title (reusable) + +on: # yamllint disable-line rule:truthy + workflow_call: + +permissions: + pull-requests: read + statuses: write + +jobs: + main: + name: Validate PR title + runs-on: ubuntu-latest + steps: + - name: Validate Conventional Commit title + id: semantic + uses: amannn/action-semantic-pull-request@v6 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + ignoreLabels: | + autorelease: pending + autorelease: tagged + + - name: Job summary + if: always() + env: + PR_TITLE: ${{ github.event.pull_request.title }} + PR_NUMBER: ${{ github.event.pull_request.number }} + PR_URL: ${{ github.event.pull_request.html_url }} + PR_HEAD: ${{ github.event.pull_request.head.ref }} + PR_BASE: ${{ github.event.pull_request.base.ref }} + run: | + { + echo "## 🏷️ Semantic PR title" + echo + echo "### πŸ”€ Pull request" + echo + echo "- **πŸ”’ Number:** #${PR_NUMBER}" + echo "- **πŸ”— URL:** ${PR_URL}" + echo "- **🌿 Branches:** \`${PR_HEAD}\` β†’ \`${PR_BASE}\`" + echo + echo "**✏️ Title:**" + echo + echo '```text' + printf '%s\n' "${PR_TITLE}" + echo '```' + echo + echo "### βš–οΈ Validation result" + echo + if [[ "${{ steps.semantic.outcome }}" == "success" ]]; then + echo "Status: **πŸŽ‰ passed** β€” title matches" + echo "[Conventional Commits](https://www.conventionalcommits.org/) (or ignored label)." + elif [[ "${{ steps.semantic.outcome }}" == "failure" ]]; then + echo "Status: **🚫 failed** β€” use a conventional title" + echo "(e.g. \`feat:\`, \`fix:\`, \`docs:\`, \`chore:\`)." + else + echo "Status: **❓ ${{ steps.semantic.outcome }}** β€” see **Validate Conventional Commit title** log." + fi + echo + echo "### 🏷️ Ignored labels" + echo + echo "Labels \`autorelease: pending\` and \`autorelease: tagged\` skip this check" + echo "(Release Please release PRs)." + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/validate-workflows.yml b/.github/workflows/validate-workflows.yml new file mode 100644 index 0000000..667de2a --- /dev/null +++ b/.github/workflows/validate-workflows.yml @@ -0,0 +1,24 @@ +--- +name: Validate workflows + +on: # yamllint disable-line rule:truthy + pull_request: + branches: + - main + push: + branches: + - main + +permissions: + contents: read + +jobs: + actionlint: + name: actionlint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: rhysd/actionlint@v1 + with: + # Markdown/code fences in job summaries intentionally use single-quoted echoes + args: -ignore SC2016 -color diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..feb99fc --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/actionlint diff --git a/README.md b/README.md new file mode 100644 index 0000000..36b22de --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ +# gha-workflows + +Reusable [GitHub Actions workflows](https://docs.github.com/en/actions/using-workflows/reusing-workflows) for OpenTofu/Terraform modules (CI, Release Please, semantic PR titles). + +## Consumers + +Module repositories call these workflows with `uses:` and a **pinned git ref** (tag or SHA), for example: + +```yaml +jobs: + pre-commit: + uses: hlvtechnologies/gha-workflows/.github/workflows/opentofu-module-ci.yml@v1.0.0 +``` + +Replace `hlvtechnologies` with your GitHub org or user if you fork this repo. + +### Available workflows + +| File | Purpose | +|------|---------| +| [opentofu-module-ci.yml](.github/workflows/opentofu-module-ci.yml) | Python, TFLint, terraform-docs, OpenTofu from `.opentofu-version`, `pre-commit run --all-files` | +| [release-please-terraform-module.yml](.github/workflows/release-please-terraform-module.yml) | [Release Please](https://github.com/googleapis/release-please) for `terraform-module` semver tags | +| [semantic-pr-title.yml](.github/workflows/semantic-pr-title.yml) | Conventional Commit PR titles (skips Release Please labels) | + +Optional `workflow_call` inputs are documented in each file (e.g. `tofu_version_file`, `terraform_docs_version`). + +### Secrets + +- **Release Please:** callers must pass `secrets: RELEASE_PLEASE_TOKEN` (see [inherit](https://docs.github.com/en/actions/using-workflows/reusing-workflows#using-inputs-and-secrets-in-a-reusable-workflow) or map explicitly). Define `RELEASE_PLEASE_TOKEN` in the **module** repository. + +## Versioning this repository + +Tag stable releases (e.g. `v1.0.0`, `v1.1.0`). Module repos should reference a tag (or immutable SHA), not a moving `main` branch, so CI changes roll out only when you bump the ref. + +After adding or changing reusable workflows: + +1. Merge to `main`. +2. `git tag v1.x.y && git push origin v1.x.y` +3. Update `uses: ...@v1.x.y` in downstream repos as needed. + +## License + +See [LICENSE](LICENSE). From 929cd56e4238dee8f4bb35612cfc096b5144acc8 Mon Sep 17 00:00:00 2001 From: Nicholas Santiago Date: Sat, 21 Mar 2026 00:31:02 -0400 Subject: [PATCH 2/5] feat: add Release Please for semantic versioning of this repo Made-with: Cursor --- .../release-please-terraform-module.yml | 2 +- .github/workflows/release-please.yml | 22 +++++++++++++++++++ .release-please-manifest.json | 3 +++ CHANGELOG.md | 5 +++++ README.md | 13 +++++++---- release-please-config.json | 10 +++++++++ 6 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/release-please.yml create mode 100644 .release-please-manifest.json create mode 100644 CHANGELOG.md create mode 100644 release-please-config.json diff --git a/.github/workflows/release-please-terraform-module.yml b/.github/workflows/release-please-terraform-module.yml index ada576f..b193901 100644 --- a/.github/workflows/release-please-terraform-module.yml +++ b/.github/workflows/release-please-terraform-module.yml @@ -78,7 +78,7 @@ jobs: echo echo "### πŸ’‘ Notes" echo - echo "- Set \`RELEASE_PLEASE_TOKEN\` (PAT). See your module repo’s \`TEMPLATE.md\`." + echo "- Set \`RELEASE_PLEASE_TOKEN\` (PAT). See the consuming repository’s docs (for example \`TEMPLATE.md\` or README)." echo "- When \`prs_created\` is \`true\`, open or refresh the **release PR** and merge to publish." echo "- \`release_created\` **true**: GitHub Release published β€” see \`html_url\`, \`tag_name\`." echo diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 0000000..35e8907 --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,22 @@ +--- +name: Release Please + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +on: # yamllint disable-line rule:truthy + push: + branches: + - main + +permissions: + contents: write + issues: write + pull-requests: write + +jobs: + release-please: + uses: ./.github/workflows/release-please-terraform-module.yml + secrets: + RELEASE_PLEASE_TOKEN: ${{ secrets.RELEASE_PLEASE_TOKEN }} diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 0000000..37fcefa --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "1.0.0" +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..c793645 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +## Changelog + +Release notes for this repository are maintained by [Release Please](https://github.com/googleapis/release-please) when you merge release pull requests on `main`. diff --git a/README.md b/README.md index 36b22de..d6e0ebd 100644 --- a/README.md +++ b/README.md @@ -19,20 +19,25 @@ Replace `hlvtechnologies` with your GitHub org or user if you fork this repo. | File | Purpose | |------|---------| | [opentofu-module-ci.yml](.github/workflows/opentofu-module-ci.yml) | Python, TFLint, terraform-docs, OpenTofu from `.opentofu-version`, `pre-commit run --all-files` | -| [release-please-terraform-module.yml](.github/workflows/release-please-terraform-module.yml) | [Release Please](https://github.com/googleapis/release-please) for `terraform-module` semver tags | +| [release-please-terraform-module.yml](.github/workflows/release-please-terraform-module.yml) | [Release Please](https://github.com/googleapis/release-please); semver tags β€” set `release-type` in your [`release-please-config.json`](release-please-config.json) (e.g. `terraform-module` for modules, `simple` for this repo) | | [semantic-pr-title.yml](.github/workflows/semantic-pr-title.yml) | Conventional Commit PR titles (skips Release Please labels) | Optional `workflow_call` inputs are documented in each file (e.g. `tofu_version_file`, `terraform_docs_version`). ### Secrets -- **Release Please:** callers must pass `secrets: RELEASE_PLEASE_TOKEN` (see [inherit](https://docs.github.com/en/actions/using-workflows/reusing-workflows#using-inputs-and-secrets-in-a-reusable-workflow) or map explicitly). Define `RELEASE_PLEASE_TOKEN` in the **module** repository. +- **Release Please:** callers must pass `secrets: RELEASE_PLEASE_TOKEN` (see [inherit](https://docs.github.com/en/actions/using-workflows/reusing-workflows#using-inputs-and-secrets-in-a-reusable-workflow) or map explicitly). Define `RELEASE_PLEASE_TOKEN` in the **calling** repository (module repos and this repo). ## Versioning this repository -Tag stable releases (e.g. `v1.0.0`, `v1.1.0`). Module repos should reference a tag (or immutable SHA), not a moving `main` branch, so CI changes roll out only when you bump the ref. +This repository uses **[Release Please](.github/workflows/release-please.yml)** on `main` with [`release-type: simple`](release-please-config.json) so tags and [CHANGELOG.md](CHANGELOG.md) follow [Semantic Versioning](https://semver.org/) from [Conventional Commits](https://www.conventionalcommits.org/) on `main`. -After adding or changing reusable workflows: +1. Add the **`RELEASE_PLEASE_TOKEN`** repository secret (fine-grained or classic PAT with **Contents** and **Pull requests** read/write for this repo; include **Workflow** if other workflows must run on release events). +2. Merge conventional commits (`feat:`, `fix:`, etc.); Release Please opens a release PR; merge it to publish a GitHub Release and tag `vX.Y.Z`. + +Downstream module repos should still pin `uses: hlvtechnologies/gha-workflows/.github/workflows/....yml@vX.Y.Z` to a **tag** (or SHA), not `main`, so workflow changes roll out only when you bump the ref after a Release Please release. + +If you add or change reusable workflows without going through Release Please (not recommended), you can still tag manually: 1. Merge to `main`. 2. `git tag v1.x.y && git push origin v1.x.y` diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 0000000..ddb1a23 --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", + "packages": { + ".": { + "changelog-path": "CHANGELOG.md", + "release-type": "simple", + "include-component-in-tag": false + } + } +} From 32611d7f1b6554a5377b033fe79330113634c686 Mon Sep 17 00:00:00 2001 From: Nicholas Santiago Date: Sat, 21 Mar 2026 00:34:42 -0400 Subject: [PATCH 3/5] fixup! actionlint --- .github/workflows/validate-workflows.yml | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/.github/workflows/validate-workflows.yml b/.github/workflows/validate-workflows.yml index 667de2a..96bcff9 100644 --- a/.github/workflows/validate-workflows.yml +++ b/.github/workflows/validate-workflows.yml @@ -17,8 +17,24 @@ jobs: name: actionlint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 - - uses: rhysd/actionlint@v1 + - name: Checkout repository + id: checkout + uses: actions/checkout@v6 + - name: actionlint + id: actionlint + uses: raven-actions/actionlint@v2.1.2 with: # Markdown/code fences in job summaries intentionally use single-quoted echoes args: -ignore SC2016 -color + - name: actionlint Summary + id: actionlint-summary + if: ${{ steps.actionlint.outputs.exit-code != 0 }} # example usage, do echo only when actionlint action failed + run: | + echo "Used actionlint version ${{ steps.actionlint.outputs.version-semver }}" + echo "Used actionlint release ${{ steps.actionlint.outputs.version-tag }}" + echo "actionlint ended with ${{ steps.actionlint.outputs.exit-code }} exit code" + echo "actionlint ended because '${{ steps.actionlint.outputs.exit-message }}'" + echo "actionlint found ${{ steps.actionlint.outputs.total-errors }} errors" + echo "actionlint checked ${{ steps.actionlint.outputs.total-files }} files" + echo "actionlint cache used: ${{ steps.actionlint.outputs.cache-hit }}" + exit ${{ steps.actionlint.outputs.exit-code }} From 370bf4528f3eb19a942e703cf7b034afad1b09e6 Mon Sep 17 00:00:00 2001 From: Nicholas Santiago Date: Sat, 21 Mar 2026 00:37:49 -0400 Subject: [PATCH 4/5] fixup! more --- .github/workflows/opentofu-module-ci.yml | 20 ++++++++++---------- .github/workflows/validate-workflows.yml | 6 ++---- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/.github/workflows/opentofu-module-ci.yml b/.github/workflows/opentofu-module-ci.yml index 046eefc..13ea307 100644 --- a/.github/workflows/opentofu-module-ci.yml +++ b/.github/workflows/opentofu-module-ci.yml @@ -96,22 +96,22 @@ jobs: echo "| Tool | Version / source |" echo "|------|------------------|" if command -v tofu >/dev/null 2>&1; then - printf '| OpenTofu | `%s` |\n' "$(tofu version 2>/dev/null | head -n1 | tr -d '\r')" + printf "| OpenTofu | \`%s\` |\n" "$(tofu version 2>/dev/null | head -n1 | tr -d '\r')" fi if command -v python3 >/dev/null 2>&1; then - printf '| Python | `%s` |\n' "$(python3 --version 2>&1 | tr -d '\r')" + printf "| Python | \`%s\` |\n" "$(python3 --version 2>&1 | tr -d '\r')" fi if command -v pre-commit >/dev/null 2>&1; then - printf '| pre-commit | `%s` |\n' "$(pre-commit --version 2>&1 | tr -d '\r')" + printf "| pre-commit | \`%s\` |\n" "$(pre-commit --version 2>&1 | tr -d '\r')" fi if command -v terraform-docs >/dev/null 2>&1; then - printf '| terraform-docs | `%s` |\n' "$(terraform-docs version 2>&1 | tr -d '\r' | head -n1)" + printf "| terraform-docs | \`%s\` |\n" "$(terraform-docs version 2>&1 | tr -d '\r' | head -n1)" fi if command -v tflint >/dev/null 2>&1; then - printf '| tflint | `%s` |\n' "$(tflint --version 2>&1 | tr -d '\r' | head -n1)" + printf "| tflint | \`%s\` |\n" "$(tflint --version 2>&1 | tr -d '\r' | head -n1)" fi if [[ -f "${TOFU_VERSION_FILE}" ]]; then - printf '| Pinned OpenTofu (file `%s`) | `%s` |\n' "${TOFU_VERSION_FILE}" "$(tr -d '\n\r' < "${TOFU_VERSION_FILE}")" + printf "| Pinned OpenTofu (file \`%s\`) | \`%s\` |\n" "${TOFU_VERSION_FILE}" "$(tr -d '\n\r' < "${TOFU_VERSION_FILE}")" fi echo echo "### πŸ§ͺ Pre-commit result" @@ -131,7 +131,7 @@ jobs: echo "
" echo "πŸ“œ Full pre-commit output" echo - echo '```text' + echo "\`\`\`text" # Cap size so the summary stays well under GitHub limits (~1 MiB) if [[ "$(wc -c < "${LOG_FILE}")" -gt 61440 ]]; then head -n 400 "${LOG_FILE}" @@ -140,7 +140,7 @@ jobs: else cat "${LOG_FILE}" fi - echo '```' + echo "\`\`\`" echo echo "
" else @@ -149,10 +149,10 @@ jobs: echo echo "### πŸ’» If checks failed locally" echo - echo '```bash' + echo "\`\`\`bash" echo "pip install pre-commit" echo "pre-commit install" echo "pre-commit install --hook-type commit-msg" echo "pre-commit run --all-files" - echo '```' + echo "\`\`\`" } >> "${GITHUB_STEP_SUMMARY}" diff --git a/.github/workflows/validate-workflows.yml b/.github/workflows/validate-workflows.yml index 96bcff9..73ec670 100644 --- a/.github/workflows/validate-workflows.yml +++ b/.github/workflows/validate-workflows.yml @@ -23,9 +23,6 @@ jobs: - name: actionlint id: actionlint uses: raven-actions/actionlint@v2.1.2 - with: - # Markdown/code fences in job summaries intentionally use single-quoted echoes - args: -ignore SC2016 -color - name: actionlint Summary id: actionlint-summary if: ${{ steps.actionlint.outputs.exit-code != 0 }} # example usage, do echo only when actionlint action failed @@ -37,4 +34,5 @@ jobs: echo "actionlint found ${{ steps.actionlint.outputs.total-errors }} errors" echo "actionlint checked ${{ steps.actionlint.outputs.total-files }} files" echo "actionlint cache used: ${{ steps.actionlint.outputs.cache-hit }}" - exit ${{ steps.actionlint.outputs.exit-code }} + # Propagate failure without a dynamic exit value (shellcheck SC2242). + exit 1 From 0aa8e3c69c160a175f343d3076198c466f5fab6f Mon Sep 17 00:00:00 2001 From: Nicholas Santiago Date: Sat, 21 Mar 2026 00:41:05 -0400 Subject: [PATCH 5/5] feat: validate conventional PR titles in this repo Made-with: Cursor --- .github/workflows/semantic-pr.yml | 23 +++++++++++++++++++++++ README.md | 2 ++ 2 files changed, 25 insertions(+) create mode 100644 .github/workflows/semantic-pr.yml diff --git a/.github/workflows/semantic-pr.yml b/.github/workflows/semantic-pr.yml new file mode 100644 index 0000000..d5d601a --- /dev/null +++ b/.github/workflows/semantic-pr.yml @@ -0,0 +1,23 @@ +--- +name: Semantic PR title + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +on: # yamllint disable-line rule:truthy + pull_request: + types: + - opened + - edited + - synchronize + branches: + - main + +permissions: + pull-requests: read + statuses: write + +jobs: + main: + uses: ./.github/workflows/semantic-pr-title.yml diff --git a/README.md b/README.md index d6e0ebd..c1ecab8 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,8 @@ Optional `workflow_call` inputs are documented in each file (e.g. `tofu_version_ This repository uses **[Release Please](.github/workflows/release-please.yml)** on `main` with [`release-type: simple`](release-please-config.json) so tags and [CHANGELOG.md](CHANGELOG.md) follow [Semantic Versioning](https://semver.org/) from [Conventional Commits](https://www.conventionalcommits.org/) on `main`. +Pull requests targeting `main` run **[semantic-pr.yml](.github/workflows/semantic-pr.yml)**, which calls the reusable [semantic-pr-title.yml](.github/workflows/semantic-pr-title.yml) so PR titles match [Conventional Commits](https://www.conventionalcommits.org/) (aligned with Release Please). Release Please release PRs can use labels `autorelease: pending` or `autorelease: tagged` to skip the title check. + 1. Add the **`RELEASE_PLEASE_TOKEN`** repository secret (fine-grained or classic PAT with **Contents** and **Pull requests** read/write for this repo; include **Workflow** if other workflows must run on release events). 2. Merge conventional commits (`feat:`, `fix:`, etc.); Release Please opens a release PR; merge it to publish a GitHub Release and tag `vX.Y.Z`.