Skip to content

loft-sh/github-actions

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

120 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

github-actions

Reusable GitHub Actions

See Pipeline Conventions for constraints on how actions are written, tested, and structured.

Available Actions

Semver Validation Action

Validates whether a given version string follows semantic versioning (semver) format.

Location: .github/actions/semver-validation

Usage:

- name: Validate version
  id: semver
  uses: loft-sh/github-actions/.github/actions/semver-validation@semver-validation/v1
  with:
    version: '1.2.3'

- name: Check if valid
  run: echo "Valid: ${{ steps.semver.outputs.is_valid }}"

Inputs:

  • version (required): Version string to validate

Outputs:

  • is_valid: Whether the version is valid semver (true/false)
  • parsed_version: JSON object with parsed version components
  • error_message: Error message if validation fails

See semver-validation README for detailed documentation.

Linear Release Sync Action

Syncs Linear issues to the "Released" state when a GitHub release is published. Finds PRs between releases, extracts Linear issue IDs, and moves matching issues from "Ready for Release" to "Released".

Location: .github/actions/linear-release-sync

Usage:

- name: Sync Linear issues
  uses: loft-sh/github-actions/.github/actions/linear-release-sync@linear-release-sync/v1
  with:
    release-tag: ${{ needs.publish.outputs.release_version }}
    repo-name: my-repo
    github-token: ${{ secrets.GH_ACCESS_TOKEN }}
    linear-token: ${{ secrets.LINEAR_TOKEN }}

See linear-release-sync README for detailed documentation.

Run Ginkgo Tests

Runs Ginkgo tests with directory or label-based filtering and generates a JSON failure summary. Runtime-agnostic — callers handle their own cluster and image setup (vind, Kind, bare Docker).

Location: .github/actions/run-ginkgo

Usage:

- name: Run E2E tests
  id: e2e
  uses: loft-sh/github-actions/.github/actions/run-ginkgo@run-ginkgo/v1
  with:
    ginkgo-label: "my-suite && !non-default"
    test-image: ghcr.io/loft-sh/vcluster:dev
    # test-image-flag: "--platform-image"  # default: --vcluster-image
    # additional-ginkgo-flags: "-v --skip-package=linters"
    # additional-args: "--use-license-server=false"

- name: Notify on failure
  if: failure()
  uses: loft-sh/github-actions/.github/actions/ci-test-notify@ci-test-notify/v1
  with:
    test-name: "E2E Tests"
    status: failure
    details: ${{ steps.e2e.outputs.failure-summary }}
    webhook-url: ${{ secrets.SLACK_WEBHOOK }}

Inputs:

Input Required Default Description
test-image yes Image passed to the test binary
test-image-flag no --vcluster-image CLI flag name for the image
timeout no 60m Ginkgo test timeout
procs no 8 Parallel Ginkgo processes
test-dir no Directory-based test selection (mutually exclusive with ginkgo-label)
ginkgo-label no Label-based test selection (mutually exclusive with test-dir)
append-pr-label no true Append || pr to the label filter
e2e-dir no e2e-next Root test directory
additional-args no Extra args for the test binary (after --)
additional-ginkgo-flags no Extra ginkgo CLI flags

Outputs:

  • failure-summary: Markdown-formatted test results summary

Available Reusable Workflows

Validate Renovate Config

Validates Renovate configuration files when they change in a pull request.

Location: .github/workflows/validate-renovate.yaml

Usage:

name: Validate Renovate Config

on:
  pull_request:

jobs:
  validate-renovate:
    uses: loft-sh/github-actions/.github/workflows/validate-renovate.yaml@main

Detected config files: renovate.json, renovate.json5, .renovaterc, .renovaterc.json, .github/renovate.json, .github/renovate.json5.

Auto-approve bot PRs

Approves (and optionally enables auto-merge on) PRs from trusted bot accounts whose title or branch matches a known safe pattern (chore: / fix(deps): / backport/ / renovate/ / update-platform-version-). Hardened to never block caller CI: continue-on-error: true on the job, every shell step catches its own errors and exits 0, self-approval is pre-empted before calling the external approve action.

Location: .github/workflows/auto-approve-bot-prs.yaml

Usage:

name: Auto-approve bot PRs

on:
  pull_request:
    types: [opened, synchronize]

jobs:
  auto-approve:
    permissions:
      pull-requests: write
      contents: read
    uses: loft-sh/github-actions/.github/workflows/auto-approve-bot-prs.yaml@main
    with:
      trusted-authors: 'renovate[bot],loft-bot,github-actions[bot],dependabot[bot]'
      auto-merge: false
    secrets:
      gh-access-token: ${{ secrets.GH_ACCESS_TOKEN }}

gh-access-token must be a PAT whose identity differs from PR authors you want to auto-approve (GitHub forbids self-review). When identity matches, the job skips gracefully instead of failing.

End-to-end coverage: scenario-level e2e lives in vClusterLabs-Experiments/auto-approve-e2e. Runs weekly and on demand. Creates real PRs exercising every decision-table branch (chore/fix(deps) titles, backport/renovate/update-platform-version branches, ineligible titles) and asserts the never-hard-fail invariant.

Actionlint

Lints GitHub Actions workflow files using actionlint with reviewdog integration.

Location: .github/workflows/actionlint.yaml

Usage:

name: Actionlint

on:
  pull_request:

jobs:
  actionlint:
    uses: loft-sh/github-actions/.github/workflows/actionlint.yaml@main

Inputs:

  • reporter (optional, default: github-pr-review): reviewdog reporter type

Publish Helm Chart

Packages a Helm chart and pushes one tarball per version to ChartMuseum. Handles release pushes (single semver, optional --app-version) and head pushes (multiple 0.0.0-* versions) under the same contract. Optionally re-pushes the repo's highest semver afterwards so it stays first in the upload-ordered ChartMuseum index.

Location: .github/actions/publish-helm-chart

Usage (release push):

jobs:
  publish-chart:
    runs-on: ubuntu-24.04
    permissions:
      contents: read
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          ref: v1.2.3
          persist-credentials: false
      - uses: loft-sh/github-actions/.github/actions/publish-helm-chart@publish-helm-chart/v2
        with:
          chart-name: vcluster
          app-version: 1.2.3
          chart-versions: '["1.2.3"]'
          chart-museum-user: ${{ secrets.CHART_MUSEUM_USER }}
          chart-museum-password: ${{ secrets.CHART_MUSEUM_PASSWORD }}

Usage (head/dev push):

jobs:
  push-head-chart:
    runs-on: ubuntu-24.04
    permissions:
      contents: read
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - uses: loft-sh/github-actions/.github/actions/publish-helm-chart@publish-helm-chart/v2
        with:
          chart-name: vcluster-head
          chart-description: "vCluster HEAD - Development builds from main branch"
          app-version: head-${{ github.sha }}
          chart-versions: '["0.0.0-latest","0.0.0-${{ github.sha }}"]'
          chart-museum-user: ${{ secrets.CHART_MUSEUM_USER }}
          chart-museum-password: ${{ secrets.CHART_MUSEUM_PASSWORD }}

Inputs:

  • chart-name (required): chart name written to Chart.yaml and used in the tarball filename
  • chart-description (optional): value written to .description in Chart.yaml
  • app-version (optional): passed as --app-version to helm package
  • chart-versions (required): JSON array of versions, e.g. '["1.2.3"]'
  • chart-directory (optional, default: chart): chart source path
  • values-edits (optional): newline-separated jsonpath=value pairs applied via yq to <chart-directory>/values.yaml
  • helm-version (optional, default: v4.1.4)
  • republish-latest (optional, default: "false"): re-push highest semver to keep it first in the ChartMuseum index
  • chart-museum-url (optional, default: https://charts.loft.sh/)
  • chart-museum-user (required)
  • chart-museum-password (required)

Note: The ref input was removed — the caller owns actions/checkout and checks out the desired ref directly.

Govulncheck

Runs govulncheck against a Go module and, on scheduled runs, posts a Slack notification (via ci-test-notify) when vulnerabilities are found. The scan always marks the job failed on vulnerabilities — notification is the side channel, not the gate.

Location: .github/actions/govulncheck

Usage (public repo, weekly schedule):

name: govulncheck

on:
  schedule:
    - cron: "0 12 * * 1" # Mon 12:00 UTC
  workflow_dispatch:
  pull_request:
    paths:
      - ".github/workflows/govulncheck.yaml"

jobs:
  scan:
    runs-on: ubuntu-latest
    if: github.repository_owner == 'loft-sh'
    permissions:
      contents: read
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - uses: loft-sh/github-actions/.github/actions/govulncheck@govulncheck/v1
        with:
          slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_URL_CI_TESTS_ALERTS }}

Usage (private repo that depends on github.com/loft-sh/*):

jobs:
  scan:
    runs-on: ubuntu-latest
    if: github.repository_owner == 'loft-sh'
    permissions:
      contents: read
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - uses: loft-sh/github-actions/.github/actions/govulncheck@govulncheck/v1
        with:
          scan-paths: "./... ./cmd/..."
          private-repo: "true"
          gh-access-token: ${{ secrets.GH_ACCESS_TOKEN }}
          slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_URL_CI_TESTS_ALERTS }}

Inputs:

  • scan-paths (optional, default: ./...): space-separated Go package patterns
  • test-flag (optional, default: true): pass -test to govulncheck
  • go-version-file (optional, default: go.mod): passed to actions/setup-go
  • private-repo (optional, default: false): enable git url rewrite + GOPRIVATE
  • goprivate (optional, default: github.com/loft-sh/*)
  • govulncheck-version (optional, default: latest)
  • test-name (optional, default: govulncheck): Slack header
  • notify (optional, default: true): send Slack on vulnerabilities; fires on schedule events only
  • gh-access-token (required when private-repo: true)
  • slack-webhook-url (required when notify: true and the run is on schedule)

Notes:

  • The caller checks out its own source and controls runs-on/timeout-minutes/fork guarding at the job level.
  • A composite action cannot declare timeout-minutes on its steps; set timeout-minutes on the caller job (default ~10m is reasonable for most modules).

Testing

Run all action tests locally:

make test

Run tests for a specific action:

make test-semver-validation
make test-linear-pr-commenter
make test-linear-release-sync

Run linters (actionlint + zizmor):

make lint

See all available targets:

make help

CI integration

Each testable action has a dedicated workflow that runs its tests on PRs when the action's files change:

  • test-semver-validation.yaml - triggers on .github/actions/semver-validation/**
  • test-linear-pr-commenter.yaml - triggers on .github/actions/linear-pr-commenter/**
  • test-linear-release-sync.yaml - triggers on .github/actions/linear-release-sync/**
  • release-linear-release-sync.yaml - builds and publishes the binary on tag push or workflow_dispatch

Each reusable workflow (workflow_call) also has a smoke/integration test workflow that triggers on PRs when the workflow file changes:

  • test-validate-renovate.yaml - calls validate-renovate.yaml with local ref. Note: When triggered by workflow YAML changes alone, the inner paths-filter won't match any renovate config files so npx renovate-config-validator never runs. The validator only exercises its full path when renovate.json is also changed.
  • test-detect-changes.yaml - calls detect-changes.yaml and asserts outputs (true/false)
  • test-actionlint-workflow.yaml - calls actionlint.yaml with github-pr-check reporter (PR-only). Note: actionlint.yaml skips fork PRs silently; the verify job emits a warning when this happens.
  • test-backport.yaml - calls backport.yaml and asserts the result is skipped
  • test-clean-github-cache.yaml - calls clean-github-cache.yaml (PR-only, since the underlying workflow needs github.event.pull_request.number)
  • test-cleanup-backport-branches.yaml - calls cleanup-backport-branches.yaml with dry-run: true
  • test-conflict-check.yaml - calls conflict-check.yaml and asserts success or skipped
  • test-claude-code-review.yaml - calls claude-code-review.yaml to validate workflow is callable
  • test-claude.yaml - calls claude.yaml and asserts skipped (no @claude comment event)
  • test-notify-release.yaml - calls notify-release.yaml with dummy inputs to validate the contract

Post-merge, dispatch-integration-tests.yaml triggers full E2E tests in vClusterLabs-Experiments/github-actions-test.

Writing tests for new actions

  1. Node.js actions - add a test/ directory with Jest tests. See semver-validation/test/index.test.js for the pattern: spawn the action's index.js with INPUT_* env vars and a temp GITHUB_OUTPUT file, then assert on the parsed outputs.

  2. Go actions - add *_test.go files next to the source. See linear-pr-commenter/src/main_test.go. Use standard go test.

  3. Composite actions (YAML-only like release-notification) - these delegate to third-party actions and have no local business logic to unit test. Validate their YAML structure through actionlint instead.

  4. Add a Makefile target for the new action following the existing pattern.

  5. Add a CI workflow at .github/workflows/test-<action-name>.yaml with a paths filter scoped to the action's directory.

  6. Add AUTO-DOC-INPUT/AUTO-DOC-OUTPUT markers to the action's README.md and run make generate-docs (see Documentation).

Documentation

Action and reusable workflow documentation is auto-generated from action.yml / workflow YAML using tj-actions/auto-doc. Each action README and each workflow doc in docs/workflows/ contains AUTO-DOC-INPUT, AUTO-DOC-OUTPUT, and AUTO-DOC-SECRETS marker comments that are filled in by the tool.

Regenerate all docs locally:

make generate-docs

Verify docs are up to date (CI runs this on every PR):

make check-docs

Install the auto-doc binary only (downloaded to .bin/):

make install-auto-doc

Workflow docs

Reusable workflow documentation lives in docs/workflows/<workflow-name>.md. Each file maps 1:1 to a workflow_call workflow in .github/workflows/.

Adding docs for a new action or workflow

  1. Action -- add ## Inputs and ## Outputs sections with marker comments to the action's README.md:

    ## Inputs
    
    <!-- AUTO-DOC-INPUT:START - Do not remove or modify this section -->
    <!-- AUTO-DOC-INPUT:END -->
    
    ## Outputs
    
    <!-- AUTO-DOC-OUTPUT:START - Do not remove or modify this section -->
    <!-- AUTO-DOC-OUTPUT:END -->
  2. Reusable workflow -- create docs/workflows/<name>.md with ## Inputs, ## Outputs (if applicable), and ## Secrets marker sections.

  3. Run make generate-docs and commit the result.

Versioning Actions

Release-notification Action

The existing release-notification action uses a repository-wide tag:

git tag -f v1
git push origin v1 --force

Referenced as:

uses: loft-sh/github-actions/release-notification@v1

New Actions

For all new actions, we use action-specific tags for independent versioning:

# For the ci-notify-nightly-tests action
git tag -f ci-notify-nightly-tests/v1
git push origin ci-notify-nightly-tests/v1 --force

# For the semver-validation action
git tag -f semver-validation/v1
git push origin semver-validation/v1 --force

# For other actions, follow the same pattern
git tag -f action-name/v1
git push origin action-name/v1 --force

Referencing Actions in Workflows

# Reference actions using their specific tag
uses: loft-sh/github-actions/.github/actions/ci-notify-nightly-tests@ci-notify-nightly-tests/v1
uses: loft-sh/github-actions/.github/actions/semver-validation@semver-validation/v1

About

Reusable GitHub acitons

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors