Skip to content

Latest commit

 

History

History
164 lines (130 loc) · 7.39 KB

CONVENTIONS.md

File metadata and controls

164 lines (130 loc) · 7.39 KB

Pipeline Infrastructure Constraints and Conventions

Binding constraints for all reusable pipeline logic in loft-sh/github-actions. Changes to this document require a PR with team review.

How Pipeline Logic is Expressed

Three tiers based on complexity:

Pattern When to use Example
YAML-only composite Thin glue wiring inputs to third-party actions, no custom logic release-notification
Compiled action (Go/Node.js) Business logic, API calls, data transformation linear-pr-commenter (Go), linear-release-sync (Go)
Composite over a released binary The logic already ships as a versioned CLI; one action installs and verifies it, the others map its answers to outputs setup-semstat (installs semstat), semver-validation (maps it)
Reusable workflow Cross-repo orchestration of multiple jobs backport.yaml, actionlint.yaml

Key rule: Business logic (branching, loops, parsing, API calls) MUST live in source files, not inline YAML run: blocks. YAML is glue only.

A composite over a released binary satisfies that rule: the logic under review lives in the binary's own repository and suite, and the scripts here only install it and translate. They may exceed the Bash line budget below, and in exchange they must pin the release, verify the download, and fail the step rather than report a broken binary's exit code as an answer.

Supported Languages

Language choice should be made on a case-by-case basis depending on action requirements. The table below captures current status and guidance:

Language Status Notes
Go Supported Org primary language. Used by linear-pr-commenter.
TypeScript Preferred for actions For runs.using: node* actions. Preferred over plain JS.
Python Supported Well-represented in AI training data, good for AI-assisted development. Adds runtime dependency overhead — use when it fits the problem best.
Bash Glue, and wrappers over a released binary Scripts under ~50 lines, or any length when they install and drive a versioned CLI (see the tier table above). Must pass shellcheck.

Exception: release-notification/detect-branch.sh is longer, because it shares release-lib's branch and version rules with platform-release, which is Bash too. Most of it guesses the branch a release was cut from, for releases whose dispatcher does not pass it. It shrinks once every dispatcher passes the branch it tagged, tracked in the DEVOPS-1598 Linear issue.

Note: Evaluate Dagger as a potential standardization layer — tracked in the DEVOPS-595 Linear issue.

Fork and Branch Compatibility

  • Explicit permissions at job level (least privilege).
  • persist-credentials: false on all checkouts.
  • Fork PRs have no secrets access — actions must skip gracefully or use a fork-detection guard:
    if: github.event.pull_request.head.repo.full_name == github.repository
  • Secrets via env: preferred over with: where possible.
  • No hardcoded branch names in reusable workflows.

Testability Requirements

Unit tests

  • Every action with business logic MUST have unit tests runnable locally.
  • Makefile target: test-<action-name>.
  • CI workflow: .github/workflows/test-<action-name>.yaml with paths filter scoped to the action's directory.
  • Tests must not require real API tokens or network access.
  • YAML-only composites are validated by actionlint + zizmor (no unit tests needed for now).
  • Testing frameworks: vitest for TypeScript, uv + pytest for Python, standard go test for Go, bats for Bash scripts. CI uses bats-core/bats-action to install bats, then runs the suite in an explicit step:
    - uses: bats-core/bats-action@<sha> # 4.0.0
      with:
        support-install: false
        assert-install: false
        detik-install: false
        file-install: false
    - name: Run <action-name> tests
      run: bats .github/actions/<action-name>/test/*.bats
    bats-action only installs bats. It does not run anything, and it has no tests input. Handing it a path is an unknown input, which is a warning rather than an error, so a job that stops there passes without executing a single test. make check-bats-jobs (run by make lint and by CI) fails the build on that shape.
  • Every test workflow's paths filter MUST include the workflow file itself, so a change to the test harness is exercised by the PR that makes it.
  • Locally, install bats with your package manager:
    # macOS
    brew install bats-core
    
    # Ubuntu / Debian
    sudo apt-get install bats
    
    # Arch Linux
    sudo pacman -S bats

Integration tests

  • For actions with complex branching logic or external action interactions, use the dedicated test repo: vClusterLabs-Experiments/github-actions-test.
  • Reference integration test results in the PR.
  • Integration tests are useful when unit tests alone cannot exercise the full workflow (e.g., cross-repo dispatch, branch protection rules).

Security Checklist

Every PR that adds or modifies an action must verify:

  • persist-credentials: false on all checkout steps
  • Explicit job-level permissions (least privilege)
  • Third-party references use full commit SHAs with version comments for Renovate
  • In-house loft-sh/* references use their rolling version tags
  • actionlint + zizmor clean (make lint)
  • Fork PRs handled (skip gracefully or guard on repo match)

CodeQL is enabled on vcluster, vcluster-pro, and loft-enterprise and will flag additional security issues in those repos.

Note: Some action runs require manual approval (e.g., runs triggered by fork PRs or workflows referencing environments with protection rules).

Directory Structure and Versioning

Actions live under .github/actions/<action-name>/. Each action is versioned independently with <action-name>/v<N> tags:

After a pull request merges to main, the action-tag workflow advances the highest existing major tag for each changed action. Before merging a change that needs a new major, add the major-release label to the pull request. The workflow then leaves every tag alone, comments on the pull request, and fails so the maintainer can cut and publish the new major. It also stops for releases that need a separate binary build, including linear-release-sync. Actions without a tag are skipped; create their first tag manually. Review callers and requirements before merging, since a compatible update reaches callers automatically.

For a manual release or recovery, the commands are:

git tag -f <action-name>/v1
git push origin <action-name>/v1 --force

Callers reference in-house actions by their rolling version tags so shared fixes reach every caller:

uses: loft-sh/github-actions/.github/actions/<action-name>@<action-name>/v1

Maintainers reserve the right to update the v<N> tag pointer at any time when a new version is released, unless Renovate is configured to handle the upgrade for a specific caller.

Reusable workflows live under .github/workflows/ and use their corresponding rolling version tags. Third-party actions and reusable workflows use full commit SHAs with version comments. Renovate configurations must exempt loft-sh/** from digest pinning.