From 099b3d74c38d83332f3a91dcff841056e44a2197 Mon Sep 17 00:00:00 2001 From: Samuel Cochran Date: Mon, 2 Nov 2026 12:01:44 +0000 Subject: [PATCH 1/4] Download releases or custom binaries without mise or compilation Support optional sha256 URL fragments and keep runtime behavior in the CLI. Amp-Thread-ID: https://ampcode.com/threads/T-e386451c-f404-41b8-8d80-f10d9ee76197 Co-authored-by: Amp --- .../plugin-native-importer-release-macos.yml | 16 - .../plugin-native-importer-release-mixed.yml | 28 - .buildkite/fixtures/plugin-source-macos.yml | 16 - .buildkite/fixtures/plugin-source-mixed.yml | 28 - .buildkite/pipeline.yml | 65 +- .github/workflows/plugin-source-smoke.yml | 23 - DEVELOPMENT.md | 59 +- README.md | 382 +--------- hooks/command | 187 +++-- lib/mise.sh | 122 --- mise.toml | 2 +- plugin.yml | 80 +- tests/command.bats | 697 +++++------------- 13 files changed, 361 insertions(+), 1344 deletions(-) delete mode 100644 .buildkite/fixtures/plugin-native-importer-release-macos.yml delete mode 100644 .buildkite/fixtures/plugin-native-importer-release-mixed.yml delete mode 100644 .buildkite/fixtures/plugin-source-macos.yml delete mode 100644 .buildkite/fixtures/plugin-source-mixed.yml delete mode 100644 .github/workflows/plugin-source-smoke.yml delete mode 100755 lib/mise.sh diff --git a/.buildkite/fixtures/plugin-native-importer-release-macos.yml b/.buildkite/fixtures/plugin-native-importer-release-macos.yml deleted file mode 100644 index 33cd20c..0000000 --- a/.buildkite/fixtures/plugin-native-importer-release-macos.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: Plugin native importer macOS-only release smoke - -on: - push: - -jobs: - native-importer-macos-only: - name: Verify released native importer macOS-only graph - runs-on: macos-14 - steps: - - name: Verify macOS runtime - shell: bash - run: | - test "${GITHUB_ACTIONS:-}" = true - test "${RUNNER_OS:-}" = macOS - test "$(uname -m)" = arm64 diff --git a/.buildkite/fixtures/plugin-native-importer-release-mixed.yml b/.buildkite/fixtures/plugin-native-importer-release-mixed.yml deleted file mode 100644 index 2b791a3..0000000 --- a/.buildkite/fixtures/plugin-native-importer-release-mixed.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Plugin native importer mixed release smoke - -on: - push: - -jobs: - native-importer-mixed-linux: - name: Verify released native importer mixed Linux job - runs-on: ubuntu-latest - steps: - - name: Verify Linux runtime - shell: bash - run: | - test "${GITHUB_ACTIONS:-}" = true - test "${RUNNER_OS:-}" = Linux - test -d /opt/hostedtoolcache - - native-importer-mixed-macos: - name: Verify released native importer Linux to macOS dependency - needs: native-importer-mixed-linux - runs-on: macos-14 - steps: - - name: Verify macOS runtime - shell: bash - run: | - test "${GITHUB_ACTIONS:-}" = true - test "${RUNNER_OS:-}" = macOS - test "$(uname -m)" = arm64 diff --git a/.buildkite/fixtures/plugin-source-macos.yml b/.buildkite/fixtures/plugin-source-macos.yml deleted file mode 100644 index ca24c94..0000000 --- a/.buildkite/fixtures/plugin-source-macos.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: Plugin macOS-only source smoke - -on: - push: - -jobs: - macos-only: - name: Verify macOS-only graph - runs-on: macos-14 - steps: - - name: Verify macOS runtime - shell: bash - run: | - test "${GITHUB_ACTIONS:-}" = true - test "${RUNNER_OS:-}" = macOS - test "$(uname -m)" = arm64 diff --git a/.buildkite/fixtures/plugin-source-mixed.yml b/.buildkite/fixtures/plugin-source-mixed.yml deleted file mode 100644 index 49c31cf..0000000 --- a/.buildkite/fixtures/plugin-source-mixed.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Plugin mixed source smoke - -on: - push: - -jobs: - mixed-linux: - name: Verify configured Linux default image - runs-on: ubuntu-latest - steps: - - name: Verify Linux runtime - shell: bash - run: | - test "${GITHUB_ACTIONS:-}" = true - test "${RUNNER_OS:-}" = Linux - test -d /opt/hostedtoolcache - - mixed-macos: - name: Verify Linux to macOS dependency - needs: mixed-linux - runs-on: macos-14 - steps: - - name: Verify macOS runtime - shell: bash - run: | - test "${GITHUB_ACTIONS:-}" = true - test "${RUNNER_OS:-}" = macOS - test "$(uname -m)" = arm64 diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 5d19f54..4c14239 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -5,7 +5,7 @@ steps: docker run --rm -t \ -v "$$PWD:/plugin" \ buildkite/plugin-tester:v4.3.0 \ - bash -c 'apk add --no-cache git && bats tests' + bash -c 'apk add --no-cache jq perl-utils && bats tests' - label: ":lint-roller: Plugin lint" key: "plugin-lint" plugins: @@ -18,7 +18,6 @@ steps: - shellcheck#v1.4.0: files: - hooks/* - - lib/* - label: ":pipeline: Load live plugin smoke" key: "live-plugin-smoke-loader" agents: @@ -97,9 +96,9 @@ steps: retry: automatic: false plugins: - - github-actions#v0.11.0: + - github-actions#$$commit: workflows: - - .buildkite/fixtures/plugin-native-importer-release-macos.yml + - .buildkite/fixtures/plugin-release-macos.yml version: 0.107.1 runners: - runs-on: macos-14 @@ -111,69 +110,15 @@ steps: timeout_in_minutes: 20 retry: automatic: false - plugins: - - github-actions#v0.11.0: - workflows: - - .buildkite/fixtures/plugin-native-importer-release-mixed.yml - version: 0.107.1 - experimental-runner-user: false - runners: - - runs-on: ubuntu-latest - queue: hosted - - runs-on: macos-14 - queue: macos - - label: ":github: Live Linux source-ref smoke" - key: "live-linux-source-ref-smoke-importer" - agents: - queue: "hosted" - timeout_in_minutes: 20 - retry: - automatic: false - env: - BUILDKITE_PLUGINS_ALWAYS_CLONE_FRESH: "true" - plugins: - - github-actions#$$commit: - workflow: .github/workflows/plugin-source-smoke.yml - source-ref: e492fcadf9b93148e5797e8be89d11ad64a220be - experimental-runner-user: true - runners: - - runs-on: ubuntu-latest - queue: hosted - - label: ":github: Live mixed source-ref smoke" - key: "live-mixed-source-ref-smoke-importer" - agents: - queue: "hosted" - timeout_in_minutes: 20 - retry: - automatic: false - env: - BUILDKITE_PLUGINS_ALWAYS_CLONE_FRESH: "true" plugins: - github-actions#$$commit: workflows: - - .buildkite/fixtures/plugin-source-mixed.yml - source-ref: e492fcadf9b93148e5797e8be89d11ad64a220be + - .buildkite/fixtures/plugin-release-mixed.yml + version: 0.107.1 experimental-runner-user: false runners: - runs-on: ubuntu-latest queue: hosted - runs-on: macos-14 queue: macos - - label: ":github: Live macOS-only source-ref smoke" - key: "live-macos-source-ref-smoke-importer" - agents: - queue: "hosted" - timeout_in_minutes: 20 - retry: - automatic: false - env: - BUILDKITE_PLUGINS_ALWAYS_CLONE_FRESH: "true" - plugins: - - github-actions#$$commit: - workflows: - - .buildkite/fixtures/plugin-source-macos.yml - source-ref: e492fcadf9b93148e5797e8be89d11ad64a220be - runners: - - runs-on: macos-14 - queue: macos YAML diff --git a/.github/workflows/plugin-source-smoke.yml b/.github/workflows/plugin-source-smoke.yml deleted file mode 100644 index a607ba2..0000000 --- a/.github/workflows/plugin-source-smoke.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: Plugin source smoke - -on: - push: - pull_request: - -jobs: - source-smoke: - name: Verify source runtime - runs-on: ubuntu-latest - steps: - - name: Verify runtime context - shell: bash - run: | - test "${GITHUB_ACTIONS:-}" = true - test "${RUNNER_OS:-}" = Linux - if [[ -n "${BUILDKITE:-}" ]]; then - test "$(id -un)" = runner - test "${HOME:-}" = /home/runner - test "${TMPDIR:-}" = /tmp/buildkite-gha-runner - fi - test -n "${GITHUB_SHA:-}" - test -n "${GITHUB_REPOSITORY:-}" diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 71e1f74..8cba249 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -1,67 +1,56 @@ # Development -Use this guide to test plugin changes, run CI smoke tests, and prepare a release. - -## Prerequisites - -Install [mise](https://mise.jdx.dev/), then install the pinned local tools: - -```bash -mise install -``` - -The release tasks also require `gh` authenticated with write access to `buildkite-plugins/github-actions-buildkite-plugin`. The CLI is installed by mise; run `mise exec -- gh auth login` and `mise exec -- gh auth setup-git` once to configure its HTTPS Git credential. - ## Run local checks -From the repository root, run: +Install [mise](https://mise.jdx.dev/), then run: ```bash +mise install mise run check ``` -The Bats suite makes no live network requests. CI runs these checks plus the Buildkite plugin linter. Importer runtime dependencies are listed in `plugin.yml`. +Mise manages contributor tools only, not importer installation. The importer dependencies are listed in [`plugin.yml`](plugin.yml). -## Run continuous integration smoke tests +The offline Bats suite tests the plugin boundary: acquisition configuration, platform selection, HTTPS download policy, checksum verification, safe extraction, unchanged CLI configuration, exit status, and temporary-file cleanup. It uses real tar archives and hashes, with a fake download transport and a recording executable. Workflow behavior belongs in the CLI's tests, not simulated CLI implementations here. ShellCheck checks the hook; CI also runs the Buildkite plugin linter. -The Buildkite Pipelines build runs required released-runtime smoke tests that: +## CI smoke coverage -- Pin the plugin to the build's full public commit SHA, except the native macOS importer regressions, which retain plugin v0.11.0. -- Pin `buildkite-gha` v0.46.2 through mise. -- Run Linux-only default-image and explicit-image jobs with the experimental `runner` user, a mixed Linux-to-macOS graph, and a macOS-only graph. +[`.buildkite/pipeline.yml`](.buildkite/pipeline.yml) owns the live runtime pins. Every importer uses the current build's full plugin commit, including native macOS importers. The released-runtime smoke graphs cover: -These tests use Linux x86-64 and native macOS arm64 Buildkite hosted agents running Agent v4, without configured secrets or a cache service. The mixed graphs explicitly disable `experimental-runner-user` to retain their existing root execution; the Linux-only graphs retain runner-user coverage despite the newer runtime's default changing to `true`. +- Linux default and explicit images with the dedicated `runner` user. +- A Linux-to-macOS dependency and a macOS-only graph, imported from Linux. +- The same mixed and macOS-only fixtures imported from native macOS arm64. -The same build also runs source smokes pinned to the v0.46.2 commit, [`e492fcadf9b93148e5797e8be89d11ad64a220be`](https://github.com/buildkite/buildkite-gha/commit/e492fcadf9b93148e5797e8be89d11ad64a220be). They verify that `source-ref` installs the required Go toolchain, builds Linux amd64, Darwin arm64, and Windows amd64 executables from the selected source, and runs Linux (including the experimental `runner` user), mixed, and macOS-only graphs without replacing the released-runtime smoke. These smokes do not execute Windows jobs. - -v0.46.2 is the first published runtime that removes `--reject-secrets` from pipeline uploads. Agent v4 removed that flag and rejects secrets by default; the former v0.17.0, native-importer v0.13.0, and source smoke pins fail on Agent v4. Keep `--no-interpolation` and default secret rejection enabled; do not set `BUILDKITE_AGENT_PIPELINE_UPLOAD_ALLOW_SECRETS=true`. See the [versioned upload contract](https://github.com/buildkite/buildkite-gha/blob/v0.46.2/docs/cli.md#upload). +These checks exercise real released CLI integration on Linux x86-64 and native macOS arm64 agents. They do not exercise Windows or Linux arm64 agents, custom binaries, secrets, or a cache service. Source builds are not part of plugin acquisition or CI. Do not add manual hosted builds or releases to local checks. ## Release the plugin -Plugin releases are immutable Git tags with asset-free GitHub Releases. Before you create a `v0.x.y` tag: +Plugin releases are immutable Git tags with asset-free GitHub Releases. The release tasks require `gh` authenticated with write access to `buildkite-plugins/github-actions-buildkite-plugin`: -1. Verify that the default runtime version has a public `buildkite/buildkite-gha` release. -1. Review the compatibility and security documentation at that runtime tag rather than using the potentially unreleased behavior on `main`. -1. Confirm that the released-runtime smoke test selects and verifies that release through mise. -1. Update every `github-actions#vX.Y.Z` reference in `README.md` to the plugin version being released; the plugin linter accepts this prospective version on `main` and requires it on the tag build. -1. Wait for the Buildkite Pipelines build for `main` to pass. +```bash +mise exec -- gh auth login +mise exec -- gh auth setup-git +``` -The Buildkite app contract changes required for server workflow selection without an explicit selector (#33143 and #33235) are deployed, and `buildkite-gha` v0.48.0 is the first runtime release that accepts selector-free configurations. Older runtimes reject them, and the server contract supplies the trusted trigger context that makes them valid. +Before tagging: -When changing runtime integration, update `plugin.yml`, `hooks/command`, `.github/workflows/plugin-smoke.yml`, `tests/command.bats`, and the README together. Make sure the examples and versioned compatibility and security links describe the recommended runtime release. +1. Confirm the recommended CLI has a public release and the documented behavior is shipped. Do not advertise custom-binary support until its companion CLI support ships. +1. Review the current product compatibility and security documentation. +1. Update every `github-actions#vX.Y.Z` reference in `README.md` to the plugin version being released. +1. Wait for the Buildkite build for `main` to pass, including released-runtime smokes. -From a clean, up-to-date local `main`, create the immutable lightweight tag: +From a clean, up-to-date local `main`: ```bash mise run release:tag vX.Y.Z ``` -The task runs the local checks and refuses a dirty, outdated, existing, or malformed release. The tag triggers another Buildkite build. Once that tag build passes every static check and live release/source smoke test, publish the validated release: +The task runs local checks and refuses dirty, outdated, existing, or malformed releases. The tag triggers another Buildkite build. After that build passes its static checks and live smokes, publish it: ```bash mise run release:publish vX.Y.Z ``` -The publish task creates or updates the GitHub Release and moves the lightweight `latest` tag with an explicit lease. It refuses to move `latest` backwards or across divergent history and is safe to rerun. CI holds no release-write credential. +The publish task creates or updates the GitHub Release and moves the lightweight `latest` tag with an explicit lease. It refuses backward or divergent promotion and is safe to rerun. CI holds no release-write credential. -Stable `vX.Y.Z` plugin tags remain immutable; `latest` is an intentionally mutable plugin alias, so `github-actions#latest` selects the newest manually promoted stable plugin release. This is separate from the plugin configuration `version: latest`, which selects the latest stable `buildkite-gha` runtime through mise. +Stable `vX.Y.Z` plugin tags stay immutable. `github-actions#latest` follows the manually promoted plugin alias; configuration `version: latest` independently resolves the latest stable CLI release through GitHub's releases API. diff --git a/README.md b/README.md index 08c690a..3ceb1bc 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,16 @@ # GitHub Actions Buildkite plugin -> [!NOTE] -> Running GitHub Actions workflows in Buildkite is currently in public preview. To report issues with the preview, [open an issue in the `buildkite-gha` repository](https://github.com/buildkite/buildkite-gha/issues). For help migrating to native Buildkite Pipelines steps, contact the [Buildkite Support team](mailto:support@buildkite.com). -> -> The plugin and runtime are under active development. Review the [`buildkite-gha` v0.97.2 compatibility guide](https://github.com/buildkite/buildkite-gha/blob/v0.97.2/docs/compatibility.md) before adding a workflow. - -The GitHub Actions Buildkite plugin converts a supported [GitHub Actions workflow](https://docs.github.com/en/actions/using-workflows/about-workflows) into native [Buildkite Pipelines](https://buildkite.com/docs/pipelines) jobs without creating a GitHub Actions workflow run. This lets you start migrating a workflow before [converting it into native Buildkite Pipelines steps](https://buildkite.com/docs/pipelines/migration/from-githubactions). - -During the preview, start with a simple workflow in a public `github.com` repository that targets Linux x86-64. Private event-repository checkout, private reusable workflows, statically named Buildkite secrets, GitHub deployment environments and variables, temporary GitHub tokens, and OIDC are available with additional setup. Check the [supported functionality and limitations](#supported-functionality-and-limitations) before you begin. +Run supported GitHub Actions workflows as native Buildkite Pipelines jobs. The plugin downloads and launches [`buildkite-gha`](https://github.com/buildkite/buildkite-gha); the CLI selects workflows, validates behavioral configuration, and uploads the generated pipeline. -## Add workflows to a pipeline +> [!NOTE] +> This integration is in public preview. See [Run GitHub Actions workflows](https://buildkite.com/docs/pipelines/migration/run-github-actions-workflows) for setup, compatibility, configuration, and security guidance. [Report issues](https://github.com/buildkite/buildkite-gha/issues) in the CLI repository. -For most users, add the plugin to a keyed command step in your pipeline configuration. Select the workflow you want to import explicitly: +## Add the plugin ```yaml steps: - label: ":github: GitHub Actions" - key: "github-actions" + key: github-actions agents: queue: importer-linux plugins: @@ -24,377 +18,71 @@ steps: workflow: .github/workflows/ci.yml ``` -An explicit selector must be a path to a `.yml` or `.yaml` workflow file. When present, the file must be regular, tracked, and inside the repository. When this importer step runs, the plugin uploads one dynamic pipeline containing a Buildkite group for each directly runnable workflow. Each workflow job and matrix entry becomes a Buildkite Pipelines job that depends on the importer step. A matrix or runner label taken from another job's output expands in a deferred step after that job runs. An explicitly configured importer step must have a `key` and must be scheduled explicitly on either a Linux amd64 or native macOS arm64 agent. The plugin's `runners` mappings schedule generated workflow jobs only; they do not select or change the importer agent. - -The Git ref after `github-actions#` selects the plugin code. Use a specific release such as `github-actions#v0.14.0` for an immutable pin, or use `github-actions#latest` to follow the newest stable plugin release that has passed the required validation. This is separate from the `version` property below, which selects the `buildkite-gha` runtime. +The importer's `agents` configuration selects its queue. Configure a Linux amd64/arm64 or native macOS arm64 agent; Windows importers are not supported. `runners` mappings select generated jobs, not the importer. -Configure runtime selection with the following properties: +The importer needs Bash, `curl`, `jq`, `tar`, `mktemp`, and `sha256sum` (Linux) or `shasum` (macOS), plus outbound HTTPS access to GitHub. See the product documentation for Buildkite Agent and generated-job requirements. -| Option | Required | Default | Description | -| --- | --- | --- | --- | -| `workflow` | Explicit selection only: one of `workflow` or `workflows` | — | One explicit `.yml` or `.yaml` workflow path. Missing or untracked paths are skipped. | -| `workflows` | Explicit selection only: one of `workflow` or `workflows` | — | Non-empty array of explicit `.yml` or `.yaml` workflow paths. Missing or untracked paths are skipped. | -| `version` | No | `latest` | Latest stable or an exact `buildkite-gha` release from `0.9.0` onward. | -| `source-ref` | No | — | `buildkite-gha` commit, branch, or tag to build for development testing; mutually exclusive with `version`. | -| `minimum-release-age` | No | `0s` | Minimum release age used by mise when resolving `latest`. | -| `experimental-runner-user` | No | `true` | Run generated Linux jobs as a dedicated `runner` user. Set to `false` only as a temporary compatibility opt-out. | -| `private-reusable-workflows` | No | `false` | Allow reusable workflows from private repositories that the importer's existing Git credentials can read. Requires `buildkite-gha` v0.65.0 or later. | -| `oidc` | No | — | Buildkite OIDC token options for jobs that request GitHub-compatible OIDC. Requires a `buildkite-gha` release with OIDC support. | -| `runners` | No | — | Authoritative `runs-on` mappings to Buildkite queues, optional immutable Linux image overrides, and optional Buildkite Hosted cache volumes. Unmapped selectors use Agent API runner resolution. | +The ref after `github-actions#` selects the plugin: use `github-actions#v0.14.0` for an immutable pin or `github-actions#latest` for the newest promoted stable plugin. The separate `version` property selects the CLI. -> [!NOTE] -> Plugin and runtime versions are independent. Pin `version` to keep release-version selection stable, or use `latest` to follow stable runtime releases. Increase `minimum-release-age` (for example, to `24h`) to delay newly published releases. If you update the runtime version, use its matching compatibility guide. - -To test unreleased runtime behavior, set `source-ref` to a full lowercase 40-character commit, branch, or tag from the public `buildkite/buildkite-gha` repository and omit `version`. Branches and tags are resolved once at importer startup and the resolved commit is logged. All runtimes use that same commit, even if the branch moves during compilation. Names shared by a branch and tag are rejected; use a full commit to disambiguate. Use a full commit when you need repeatable builds across runs. - -The plugin uses mise and Go 1.26.10 to build Linux amd64, Darwin arm64, and Windows amd64 executables from that exact source, runs the executable native to the importer agent, and supplies the other runtimes to generated jobs. Testing experimental Windows jobs requires a source commit that supports Windows and a compatible Windows queue; the importer still runs on Linux amd64 or Darwin arm64. Source builds do not include a Linux arm64 runtime, so workflows with jobs mapped to Linux arm64 queues fail unless you use a released `version`. The compiler pin applies only to fresh source builds; it does not rebuild released binaries or replace cached release assets. Source builds are for development only and do not use release checksums, attestations, or `minimum-release-age`. +## Select the executable -The plugin schema validates explicit selector paths when present, the runtime-acquisition fields `version`, `source-ref`, and `minimum-release-age`, the boolean `experimental-runner-user` field, and the admission-level shape of `oidc`. It passes behavioral configuration, such as `runners` and `private-reusable-workflows`, through to the selected `buildkite-gha` runtime, which validates the complete configuration strictly. This allows runtime releases to extend the supported syntax without requiring a companion plugin release. +| Property | Default | Description | +| --- | --- | --- | +| `version` | `latest` | Latest stable CLI release or an exact release tag, with or without `v`, including prereleases. | +| `binary` | — | HTTPS URL of a raw importer executable, or a map of platform names to executable URLs. Mutually exclusive with an explicit `version`. Requires companion CLI support; see below. | -### Select workflows +`latest` resolves once through GitHub's releases API. The plugin downloads the selected platform's official archive and `checksums.txt` from that exact release, verifies SHA256, and extracts only the executable. It uses a private temporary directory, removes it on exit, and keeps no installer cache. It does not install mise or build Go source. -Use `workflow` as the simple form for one explicit path: +To pin a CLI release: ```yaml plugins: - github-actions#latest: workflow: .github/workflows/ci.yml + version: "0.107.1" ``` -Use the non-empty `workflows` array when importing multiple explicit paths: - -```yaml -plugins: - - github-actions#latest: - workflows: - - .github/workflows/ci.yml - - .github/workflows/release.yml -``` - -For explicit selection, configure exactly one selector form. Each present value must identify one regular, tracked `.yml` or `.yaml` file inside the repository. Empty values and arrays, directories, globs, symlinks, files outside the repository, and wildcard selectors are not accepted. Selected paths are canonicalized, sorted, and deduplicated before upload. - -Missing or untracked configured paths produce a warning and are skipped. If all configured paths are missing or untracked, the importer succeeds without uploading a pipeline. Remaining workflows are compiled and uploaded in one pipeline transaction. Workflow groups use the workflow's `name`, falling back to its repository path; a supported non-empty `run-name` is appended to the group label. Reusable workflows whose only trigger is `workflow_call` do not create groups, but remain available to matched callers. Selecting only reusable workflows is an error. A parse, compilation, or trigger-translation error in one of several selected workflows instead becomes a failing top-level step, allowing other selected workflows to remain in the uploaded pipeline. - -### Run Linux jobs as a runner user - -Generated Linux job processes run as a dedicated `runner` user by default. To temporarily run as the agent's original user without this bootstrap, set `experimental-runner-user: false`: - -```yaml -steps: - - label: ":github: GitHub Actions" - key: "github-actions" - agents: - queue: hosted - plugins: - - github-actions#latest: - workflow: .github/workflows/ci.yml - version: "0.97.2" - experimental-runner-user: false - runners: - - runs-on: ubuntu-latest - queue: hosted -``` - -Generated Linux jobs must start as root, or as an existing `runner` user with home `/home/runner` and passwordless `sudo`, so the runtime can provision the `runner` account. That user retains passwordless `sudo`, so this is not a security boundary. The option does not affect macOS or Windows jobs. - -### Configure OIDC tokens - -Use the `oidc` block to configure pipeline-owner options for GitHub-compatible OIDC tokens: - -```yaml -plugins: - - github-actions#latest: - workflow: .github/workflows/deploy.yml - oidc: - claims: [organization_id] - aws-session-tags: [organization_slug, pipeline_id] - subject-claim: pipeline_id -``` - -This configuration requires a `buildkite-gha` release with OIDC support. Releases without that support reject the `oidc` block during strict behavioral configuration validation, so do not enable it until a supporting runtime release is selected. - -The block only affects jobs that already declare `permissions: id-token: write`; it does not grant OIDC access to other jobs or change the workflow itself. Host JavaScript actions, including those called by composite actions, can request these tokens; shell steps, Docker actions, and actions running in job containers cannot. Identity providers must trust Buildkite's issuer and claims rather than GitHub's. `claims` adds optional claims to tokens, `aws-session-tags` duplicates claims into AWS session-tag format, and `subject-claim` selects one immutable claim as the token subject. The accepted values match [`buildkite-agent oidc request-token`](https://buildkite.com/docs/agent/cli/reference/oidc) and are validated by `buildkite-gha`. - -The supported top-level triggers select and filter workflow groups as follows: - -| GitHub Actions trigger | Buildkite behavior | -| --- | --- | -| `push` | GitHub `push` webhook, including supported branch, tag, and bounded path filters; also Buildkite UI or API build | -| `pull_request` | GitHub `pull_request` webhook, including supported base-branch, activity-type, and bounded path filters | -| `merge_group` | Native Buildkite merge queue build with a verified linked `checks_requested` webhook | -| `release` | Native Buildkite release build with a verified linked `published`, `created`, or `released` webhook | -| `issues` | Native Buildkite issue build with verified linked webhook data | -| `workflow_dispatch` | Build with an authoritative `workflow_dispatch` value in `GITHUB_EVENT_NAME` or `BUILDKITE_GITHUB_EVENT` | -| `schedule` | Buildkite scheduled build | - -[GitHub Actions Pipeline Triggers](#run-from-a-github-actions-pipeline-trigger) support additional events. The [compatibility guide](https://github.com/buildkite/buildkite-gha/blob/v0.97.2/docs/compatibility.md#names-and-triggers) lists every supported event and filter. `pull_request_target` is intentionally unsupported. - -The effective event selects which workflow groups apply. Applicable workflows use only the matching event's condition; triggers for other events are not ORed into that group. Unsupported events alongside supported events are ignored with a warning. Bounded `paths` and `paths-ignore` filters are supported for verified linked GitHub branch pushes and pull requests when the local checkout provides complete matching diff evidence; a verified nonmatch becomes a skipped step. Other unsupported or inexact filters fail the affected workflow rather than broadening when it runs. A `workflow_call` trigger defines a reusable workflow and does not create a top-level group by itself. - -These conditions select groups in a Buildkite build; they do not configure which GitHub webhooks create builds. Configure the corresponding webhook events in the Buildkite pipeline settings. Buildkite also retains ownership of cron schedules: every workflow with `on.schedule` is eligible during any Buildkite scheduled build. - -## Migrate incrementally - -Imported workflow jobs and native Buildkite Pipelines steps can run in the same build. In this example, the native `Deploy` step waits for all imported test jobs to finish: - -```yaml -steps: - - label: ":github: Tests" - key: "github-actions-tests" - plugins: - - github-actions#latest: - workflow: .github/workflows/ci.yml - - - label: "Deploy" - key: "deploy" - depends_on: "github-actions-tests" - command: ".buildkite/deploy.sh" -``` - -As you replace jobs with native Buildkite Pipelines steps, the remaining supported workflow jobs can keep running through the plugin. - -## How the plugin works - -The plugin and the `buildkite-gha` runtime work together to run the workflow: - -- The plugin uses an existing compatible `mise`, or installs a pinned verified copy, then asks mise to select and run the configured `buildkite-gha` release or source commit. -- The hidden `buildkite-gha plugin` command reads the plugin configuration, checks that the workflow is supported, converts its jobs into Buildkite Pipelines command jobs, uploads them, and runs each generated job. - -You do not need to install `mise` or `buildkite-gha`. The importer requires mise 2026.10.17 or newer; older copies on `PATH` or in the managed cache are not reused. If needed, it bootstraps mise 2026.10.17 from a platform-specific, SHA-256-verified upstream archive. This minimum includes the eMudhra intermediate-certificate distrust updates. It applies to the importer only: generated-job mise installations and precompiled runtimes are managed by the selected `buildkite-gha` release and must be updated independently. Mise selects, verifies, and caches the release asset matching the importer host: Linux amd64 or Darwin arm64. The importer then verifies and stages each other same-release runtime (Linux x86-64, Linux arm64, macOS arm64, or Windows x86-64) only when a generated job needs it. +`source-ref` and `minimum-release-age` are retired and fail with an error. Publish a built executable and use `binary` to test unreleased code; use an exact `version` instead of delaying `latest`. -Generated jobs that use JavaScript actions also prepare a verified, managed `mise` installation for the supported Node.js versions. Shell-only generated jobs and jobs that use only native adapters or Docker do not install `mise`. - -The importer passes the runtime and compiled execution plans to generated jobs using Buildkite Pipelines artifacts. Each job verifies these files before using them. Buildkite Pipelines handles scheduling, logs, retries, cancellation, and build status. - -## Requirements - -The importer step needs: - -- A Linux amd64 or Darwin arm64 agent, selected by the importer's own `agents` configuration. Generated-job `runners` mappings do not schedule this step. -- Buildkite Agent v4 with `buildkite-gha` v0.46.2 or later to preserve default secret rejection during pipeline upload. Older runtimes pass `--reject-secrets`, which Agent v4 removed; use Agent v3.129 or later in the v3 series with those runtimes. Do not enable `BUILDKITE_AGENT_PIPELINE_UPLOAD_ALLOW_SECRETS` on Agent v4. -- Bash, `curl`, `tar`, `mktemp`, `cp`, and either `sha256sum` on Linux or `shasum` on macOS, as listed in [`plugin.yml`](plugin.yml). The download tools are used only when a compatible `mise` is not already on `PATH`. -- Git when `BUILDKITE_COMMIT` is not already a full commit SHA. -- Outbound HTTPS access to public GitHub release and action sources. - -Generated jobs need Buildkite agent v3.129 or later and an execution environment matching their runner mapping. Linux x86-64 jobs can run on [Buildkite hosted agents](https://buildkite.com/docs/agent/buildkite-hosted), the [Agent Stack for Kubernetes](https://buildkite.com/docs/agent/self-hosted/agent-stack-k8s), or other self-hosted agents that provide the workflow's tools. Linux arm64 jobs require an explicitly mapped Linux arm64 queue and `buildkite-gha` v0.97.0 or later. Supported macOS labels require a native Darwin arm64 queue. [Experimental Windows jobs](https://github.com/buildkite/buildkite-gha/blob/v0.97.2/docs/compatibility.md#experimental-windows-jobs) require a Windows Server 2022 x86-64 queue with PowerShell 7 (`pwsh`) and Git on `PATH`. The runtime tells the agent to skip its usual repository checkout so that it can prepare the workflow workspace instead. - -Depending on the workflow, generated-job hosts also need: - -- `git` available on `PATH` for `actions/checkout`. -- Docker available on `PATH` for Linux job containers, service containers, and Docker actions. Dockerfile actions also require Docker Buildx, whose default builder must use the local `docker` driver. - -## Map runner labels to queues and images - -Use `runners` to map an exact GitHub `runs-on` label to a Buildkite queue. An explicit mapping is authoritative and bypasses Agent API resolution. Unmapped selectors are resolved by the job-scoped Buildkite Agent API. If the Agent API cannot be reached, the importer warns and falls back to local presets for `ubuntu-latest`, `ubuntu-24.04`, `ubuntu-22.04`, and `macos-latest`. Configured `ubuntu-latest` and `ubuntu-24.04` profiles use the Noble hosted-toolchains image by default; `ubuntu-22.04` uses Jammy. A Linux x86-64 mapping may override that default with another digest-pinned image: - -```yaml -steps: - - label: ":github: GitHub Actions" - key: "github-actions" - agents: - queue: importer-macos-arm64 - plugins: - - github-actions#latest: - workflow: .github/workflows/ci.yml - runners: - - runs-on: ubuntu-latest - queue: hosted - image: buildkite.namespace-images.com/agent-base@sha256:62a45683afffaae9edfd669c16d2fee23b5a571679f31715e1063dada667ea24 - - runs-on: macos-14 - queue: macos-sonoma-arm64 -``` +### Use a custom executable -The top-level `agents.queue` above schedules the importer on macOS arm64; it is independent of the queues under `runners`. Labels are matched case-insensitively after expressions and matrices are resolved. An explicit `image` applies only to the matching Linux x86-64 label, must be an immutable `@sha256:` reference, and replaces the label's hosted-toolchains default. macOS mappings select a native queue and cannot specify an image. Duplicate labels, unsupported labels, malformed queues or images, and conflicting multi-label targets fail admission. +> [!IMPORTANT] +> `binary` requires the companion `buildkite-gha` CLI support and is not supported by existing released CLIs. Use a build containing that support; older CLIs reject this property. -An explicit mapping declares that its label runs on Linux x86-64, except for known Linux arm64 labels (`ubuntu-24.04-arm`, `ubuntu-22.04-arm`, and labels ending in `-arm64` or `-aarch64`), known macOS labels, and experimental Windows labels (`windows-latest` and `windows-2022`). Linux arm64 labels have no default mapping. Windows labels are rejected unless explicitly mapped or enabled through Agent API resolution. Map them to your own queues: +Use a raw executable URL, not an archive, local path, or URL template. Prefer an immutable URL with a `#sha256=` fragment containing its 64-digit hexadecimal SHA256. The fragment is removed before download, and the executable bytes are verified before execution. Plain HTTPS URLs remain available for development; other fragments are rejected. ```yaml plugins: - github-actions#latest: workflow: .github/workflows/ci.yml - runners: - - runs-on: ubuntu-24.04-arm - queue: my-linux-arm64-queue - - runs-on: windows-2022 - queue: my-windows-queue + binary: "https://example.com/buildkite-gha-linux-amd64#sha256=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" ``` -Linux arm64 and Windows mappings cannot specify an image. They do not create queues, enable Buildkite hosted Linux arm64 or Windows agents, or fall back to x86-64 emulation. - -For other selectors, the Agent API returns the complete queue, platform, and host environment, either an immutable Linux image or a native Linux host selected through agent tags, and may return a fallback warning. If the Agent API rejects a selector, for example because the job's cluster has no compatible hosted queue, the importer reports the reason at the job's `runs-on` before uploading the pipeline. - -> [!WARNING] -> Generated jobs may execute untrusted workflow or action code. The selected queue must provide whole-job isolation, no ambient protected credentials, and a clean machine for each untrusted job. Persistent self-hosted agents can expose host resources and state left by earlier jobs. - -### Configure generated-job cache volumes - -An explicit Linux runner mapping can attach one [Buildkite Hosted cache volume](https://buildkite.com/docs/agent/buildkite-hosted/cache-volumes) to every generated job using that mapping: +Replace the example URL and checksum with your executable's values. A single URL covers the importer platform only. For workflows that need other platforms, supply a map with the importer and each required counterpart: ```yaml plugins: - github-actions#latest: workflow: .github/workflows/ci.yml - runners: - - runs-on: ubuntu-latest - queue: hosted - cache: - paths: - - /home/runner/.gradle/caches - - /home/runner/.gradle/wrapper - name: gradle-${BUILDKITE_BRANCH} - size: 40g -``` - -`cache.paths` is a required, non-empty list of unique absolute paths. `name` and `size` are optional; names are at most 100 characters and use Buildkite's letters, numbers, hyphens, and `${BUILDKITE_*}` variables, while sizes use `Ng` and must be at least `20g`. The runtime merges its managed mise cache into the same volume when needed. Cache volumes are unsupported for workflow jobs that set `container` and for Windows jobs. - -Cache volumes are best-effort, pipeline-and-cluster-scoped accelerators that commit only after successful jobs. Treat their contents as untrusted executable state, and do not use them as durable storage. This configuration is separate from the workflow's `actions/cache` behavior. - -## Configure generated-job runtimes - -Generated jobs need network access for anything they download at runtime: - -- Jobs that use public GitHub Actions need outbound HTTPS access to `codeload.github.com`, where the runtime downloads each action's source archive. -- Jobs that use JavaScript actions need outbound HTTPS access to the managed Node.js and `mise` downloads. Actions that declare `node16` run on managed Node 16.20.2 and produce a deprecation warning. Actions that declare `node20` or `node24` run on managed Node 24.18.0. Managed Node binaries require glibc 2.28 or newer. Shell-only workflows do not have this glibc requirement. - -When resolving a mutable tag or branch for a public action, the importer uses a dedicated action-source token only for public GitHub metadata requests and reuses it across the selected workflows and nested composite actions. If it cannot obtain the token, it reports a warning and retries anonymously. A lowercase, full 40-character commit SHA does not require an API request. Credential-repository metadata requests and all action archive downloads from `codeload.github.com` remain anonymous. - -Configured Linux profiles select an immutable hosted-toolchains image and enable its `/opt/hostedtoolcache`. An explicit `runners[].image` override must provide the same tool-cache path and is supported only when the matching jobs run on Buildkite hosted agents or Agent Stack for Kubernetes controller v0.30.0 or later. Do not configure Linux profiles for other self-hosted environments that cannot provision the generated job image. Native Linux hosts selected by the Agent API use a job-private tool cache. macOS and Windows profiles never select an image. - -## Configure triggers and GitHub context - -Buildkite Pipelines controls when builds run. Configure branch, tag, schedule, and pull request triggers in Buildkite. The workflows' `on` keys select groups after a build exists; they do not create Buildkite Pipelines triggers. - -For manual and scheduled builds, the plugin finds the exact commit from the checked-out repository when `BUILDKITE_COMMIT` does not already contain a full commit SHA. - -Pull request builds receive `pull_request` context. Branch and tag builds, and manual UI or API builds, receive `push` context. Verified linked merge queue, release, and issue webhooks supply `merge_group`, `release`, and `issues` context. Buildkite scheduled builds select workflows with a `schedule` trigger. `workflow_dispatch` is selected only when the build supplies it as an authoritative `GITHUB_EVENT_NAME` or `BUILDKITE_GITHUB_EVENT` value; dispatch inputs are not available. - -When the build has linked webhook data, `GITHUB_EVENT_PATH` points to a job-scoped file containing the complete event payload. The importer stores the payload as a build artifact, so anyone who can download the build's artifacts can read it. Builds without linked webhook data, such as manual and scheduled builds, do not create this file. - -### Run from a GitHub Actions Pipeline Trigger - -> [!NOTE] -> GitHub Actions Pipeline Triggers are in private preview and feature flagged off for most organizations. - -When this trigger type is enabled, the Buildkite app and `buildkite-gha` select the workflow associated with the trigger. The minimal pipeline configuration is: - -```yaml -steps: - - plugin: github-actions -``` - -Server-selected imports require `buildkite-gha` v0.48.0 or later. The importer step does not need a `key`, and generated jobs are uploaded without a workflow group. Unlike an explicitly configured path, a missing or untracked server-selected workflow fails the importer. An explicit `workflow` or `workflows` value takes precedence over server selection. - -Pipeline Triggers support more events than native Buildkite builds, including all seven `release` activities, tokenless `merge_group` workflows, `deployment`, `deployment_status`, `issue_comment`, same-repository `pull_request_review` and `pull_request_review_comment`, and repository lifecycle events such as `create`, `delete`, `label`, and `discussion`. See the [server-selected event contract](https://github.com/buildkite/buildkite-gha/blob/v0.97.2/docs/cli.md#private-preview-pipeline-trigger-selection) and the [compatibility guide](https://github.com/buildkite/buildkite-gha/blob/v0.97.2/docs/compatibility.md#names-and-triggers) for each event's requirements. - -Supported runtime and behavioral options may still be configured using the standard `plugins` form while omitting both `workflow` and `workflows`; the plugin forwards them without inferring trigger context. - -For example, test a runtime branch while keeping server-side workflow selection and automatic runner mapping: - -```yaml -steps: - - key: github-actions - agents: - queue: importer-linux - plugins: - - github-actions#latest: - source-ref: main + binary: + linux/amd64: https://example.com/buildkite-gha-linux-amd64 + linux/arm64: https://example.com/buildkite-gha-linux-arm64 + darwin/arm64: https://example.com/buildkite-gha-darwin-arm64 + windows/amd64: https://example.com/buildkite-gha-windows-amd64.exe ``` -Replace `main` with the runtime branch to test and `importer-linux` with your Linux amd64 or macOS arm64 importer queue. The server supplies the workflow context for the build, and the Agent API maps its `runs-on` labels to available queues. No `workflow`, `workflows`, or `runners` override is needed. This requires a build with server-selected workflow context; ordinary builds without that context still need an explicit workflow selector. Automatic mapping does not enable unavailable platforms or provision missing queues. - -## Configure checkout and credentials - -Supported `actions/checkout` revisions can check out the event repository at its event commit or a static branch. Commits outside the audited snapshot use the latest stable checkout contract and produce a warning. Checkout runs anonymously when repository-provider credentials are not enabled. Private checkout uses Buildkite repository-provider Git credentials when they are enabled and authorized for the job. Direct and recursive GitHub submodules are supported within the compatibility guide's transport and credential boundaries. - -Checkout credentials do not populate `GITHUB_TOKEN` or `github.token`, enable private actions, or allow alternate repositories, tags, or arbitrary dynamic commits. A workflow can receive a temporary GitHub token only when it makes a supported static token reference and both the Buildkite organization feature and the pipeline's default-off token setting are enabled. When the workflow omits `permissions`, the runtime requests exactly `contents: read` without inheriting GitHub repository or organization defaults. Write access requires an explicit top-level permissions map; an empty map or scopes set to `none` mint no token. - -> [!WARNING] -> Temporary token issuance verifies the workflow and build provenance. Job-level repository permission maps are accepted but do not alter `GITHUB_TOKEN`; job-level `id-token` permissions retain their documented behavior. Jobs expanded from reusable workflows use the top-level requesting workflow's repository permissions because called-workflow permission maps do not narrow `GITHUB_TOKEN`. Pull request ancestry is capped at `contents: read`, and merge queue ancestry is denied. Review the workflow-token restrictions before enabling the service. - -Direct jobs can resolve statically named `${{ secrets.NAME }}` references through the destination job's Buildkite secret authority. Local reusable-workflow calls can use `secrets: inherit` or explicitly map declared aliases from direct `${{ secrets.NAME }}` references; inheritance is one hop and must be repeated at each nested call. These are Buildkite secrets, not GitHub repository, environment, event, or fork-scoped secrets; [environment secret names](#deployment-environments-and-variables) map to Buildkite secret names. Dynamic secret names and secret forwarding to remote reusable workflows are unsupported. The compatibility guide describes the [complete credential boundary](https://github.com/buildkite/buildkite-gha/blob/v0.97.2/docs/compatibility.md#repositories-credentials-and-github-services). - -GitHub's API cannot export secret values. To copy GitHub Actions repository secrets into Buildkite secrets, use [`buildkite-gha migrate-secrets`](https://github.com/buildkite/buildkite-gha/blob/v0.97.2/docs/cli.md#migrate-github-actions-secrets), which generates a reviewable, one-use GitHub Actions workflow. - -### Deployment environments and variables - -Top-level jobs can declare a literal GitHub [deployment environment](https://github.com/buildkite/buildkite-gha/blob/v0.97.2/docs/compatibility.md#deployment-environments). The Buildkite backend reads its protection rules, secret names, and variables from GitHub; no GitHub token or secret value reaches the importer. Required reviewers become a Buildkite block step that any user who can unblock the pipeline can approve. An environment secret `NAME` resolves to the Buildkite secret `_`, for example `PRODUCTION_DEPLOY_KEY`. Wait timers, deployment branch policies, and custom protection rules fail compilation, and builds do not create GitHub deployment records. - -`${{ vars.NAME }}` resolves [repository, organization, and environment variables](https://github.com/buildkite/buildkite-gha/blob/v0.97.2/docs/compatibility.md#repository-and-organization-variables) with GitHub's per-position scoping. Variable values are configuration, not secrets: they are stored in the job plan artifacts and are visible to anyone who can read the build's artifacts. - -### Private reusable workflows - -Set `private-reusable-workflows: true` to call reusable workflows from the pipeline repository or other private GitHub repositories: - -```yaml -plugins: - - github-actions#latest: - workflow: .github/workflows/ci.yml - private-reusable-workflows: true -``` - -The importer resolves these workflows with its existing Git credentials, such as the Buildkite Agent repository-provider credential helper, which requests approval for each repository. Missing and denied repositories, refs, and paths produce the same error. Private actions remain unsupported. +Each map URL also accepts a checksum fragment. The plugin downloads only its importer executable and forwards the configuration unchanged. The companion CLI downloads and verifies counterparts only when needed, with no release fallback for a missing platform. Use matching builds across the map. Windows amd64 is a counterpart platform only. > [!WARNING] -> Access is repository-wide: once enabled, workflow source can select any workflow in any GitHub repository the importer's credentials can read. Restrict the importer's Git credentials or use Buildkite's repository-provider access policy to approve only the required repositories. See the [`buildkite-gha` security model](https://github.com/buildkite/buildkite-gha/blob/v0.97.2/docs/security.md). - -## Cache mise installations - -On Buildkite hosted agents, attach a mise data cache to avoid reinstalling mise and `buildkite-gha`: - -```yaml -steps: - - label: ":github: GitHub Actions" - key: "github-actions" - cache: "/cache/bkcache/mise" - plugins: - - github-actions#latest: - workflow: .github/workflows/ci.yml -``` - -Without this volume, mise uses the agent or user data directory. Treat the mise data directory as executable state: do not share it with untrusted jobs or principals that can modify it. This importer cache is separate from generated-job runtime caching and the workflow's `actions/cache` behavior. - -## Supported functionality and limitations - -The public preview supports an evolving subset of GitHub Actions. Common supported features include: +> Custom URLs select code that runs with the importer's authority. HTTPS protects transport, not publisher trust; a checksum pins bytes but does not establish who built them. Use trusted publishers and do not put credentials in URLs. URLs remain in pipeline configuration and may appear in errors. -- Linux x86-64 jobs using `ubuntu-latest`, `ubuntu-24.04`, or `ubuntu-22.04`. These labels identify a compatible runner but do not provide the same tools or image layout as a GitHub-hosted runner. -- Linux arm64 jobs mapped explicitly to a Linux arm64 queue, such as `ubuntu-24.04-arm`. -- Native macOS Apple Silicon jobs using `macos-latest`, `macos-15`, or `macos-14` when the Agent API, local preset, or a configured fallback resolves the label to a Darwin arm64 queue. -- [Experimental Windows Server 2022 x86-64 jobs](https://github.com/buildkite/buildkite-gha/blob/v0.97.2/docs/compatibility.md#experimental-windows-jobs) using `windows-latest` or `windows-2022` with an explicit mapping or Agent API routing. -- Bash, `sh`, `pwsh`, `powershell`, `python`, and custom shell-template run steps on Linux and macOS when the selected interpreter is available on `PATH`. Windows jobs default to `pwsh` and support Windows PowerShell and MSYS2 custom shells. -- Static job dependencies and matrices, including compile-time expression-valued dimensions, `include`, and `exclude`, up to 256 expanded instances per job. -- [Matrices](https://github.com/buildkite/buildkite-gha/blob/v0.97.2/docs/compatibility.md#matrices-from-job-outputs) and [runner labels](https://github.com/buildkite/buildkite-gha/blob/v0.97.2/docs/compatibility.md#runners-from-job-outputs) taken from a job output, such as `fromJSON(needs.plan.outputs.matrix)`, which expand in a deferred step after the producing job runs. -- Supported field-specific expressions in job and step conditions, names, runner selection, `env` maps, defaults, outputs, matrices, concurrency, and reusable-workflow calls. Job timeouts remain literal-only. -- Supported outputs, timeouts, and expression-capable job- and step-level `continue-on-error`. Runtime `runner.temp` is available in supported workflow step fields, step conditions, and job outputs, but not job conditions or compile-time positions. -- Workspace-confined `hashFiles()` in supported step conditions and step runtime fields, with bounded patterns, matches, and input size. -- Public JavaScript, composite, local, self-repository (`$/`), compiler-verified Dockerfile, and public prebuilt-image Docker actions. Direct workflow `uses: docker://...` steps and private action images remain unsupported. -- Local, literal public, and opt-in private reusable workflows. Local calls can inherit or explicitly map declared Buildkite secret authority. String inputs can embed direct dependencies' `needs` outputs, and boolean or number inputs can derive from them through a single expression. -- Linux job and service containers, including broadly compatible service health checks, credentials, ports, volumes, and the `job.services` context. -- Statically named Buildkite secrets in direct jobs and opt-in temporary `GITHUB_TOKEN` and OIDC support within the documented authority boundaries. -- Literal deployment environments on top-level jobs with required-reviewer approval gates and environment secret names, plus repository, organization, and environment `vars`. -- The complete event payload through `GITHUB_EVENT_PATH` for builds with linked webhook data. -- Supported revisions of `actions/checkout` (including nested paths, LFS, sparse checkout, and partial-clone filters), `actions/upload-artifact`, `actions/download-artifact`, and `actions/cache`. See the compatibility guide for exact admitted commits and version-specific behavior. +## Configure workflow behavior -Important limitations include: - -- GitHub-stored secret values, ambient `GITHUB_TOKEN`, private actions, alternate-repository checkout, tags, and arbitrary dynamic checkout commits are not available. -- Windows arm64, Windows Server 2025, and macOS x86-64 jobs are not supported. Linux arm64 and Windows jobs need your own queues. -- macOS does not provide GitHub-hosted image or Xcode inventory parity. Docker actions, job containers, and service containers are not supported on macOS or Windows. -- Dynamically selected reusable workflows are not supported. A job that takes its runner label from a job output cannot also declare a matrix. -- Environment wait timers, deployment branch policies, custom protection rules, and GitHub deployment records are not supported. GitHub reviewer lists are not enforced; anyone who can unblock the pipeline can approve. -- The runtime accepts `strategy.fail-fast` but does not enforce it, so a failed matrix job does not cancel the other matrix jobs. -- `github.event_path` expressions are not supported, and builds without linked webhook data do not provide `GITHUB_EVENT_PATH`. -- Unaudited `actions/checkout`, `actions/upload-artifact`, and `actions/download-artifact` commits use the latest stable contract with a warning; unaudited `actions/cache` commits run a substituted audited release. - -If a feature is not listed in the [`buildkite-gha` v0.97.2 compatibility guide](https://github.com/buildkite/buildkite-gha/blob/v0.97.2/docs/compatibility.md), treat it as unsupported. +The plugin validates acquisition properties only. All other options, including `workflow`, `workflows`, `runners`, and `oidc`, pass unchanged to the selected CLI for strict validation. See the [product documentation](https://buildkite.com/docs/pipelines/migration/run-github-actions-workflows) for supported options and examples rather than assuming a newer option works with an older CLI. > [!WARNING] -> All steps in an imported job share a workspace, environment changes, processes, and action lifecycle. Docker actions provide packaging, not a security boundary. Review the [`buildkite-gha` v0.97.2 security model](https://github.com/buildkite/buildkite-gha/blob/v0.97.2/docs/security.md) before running untrusted workflow code. - -## Develop the plugin - -See the [development guide](DEVELOPMENT.md) for local tests, CI smoke tests, and release instructions. +> Generated jobs can run untrusted workflow and action code. Use whole-job isolation, clean machines, and no ambient protected credentials. Review the product's security guidance before enabling private repository access, secrets, or temporary GitHub tokens. -## License +## Development and license -This project uses the MIT License. See [LICENSE](LICENSE). +See [Development](DEVELOPMENT.md) for local checks, CI coverage, and release safety. This project uses the [MIT License](LICENSE). diff --git a/hooks/command b/hooks/command index 6dac3c0..1cf72d5 100755 --- a/hooks/command +++ b/hooks/command @@ -2,122 +2,103 @@ set -euo pipefail umask 077 -PLUGIN_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -mise_data_dir_override="${MISE_DATA_DIR:-}" -for variable in "${!MISE_@}"; do - unset "$variable" -done -if [[ -n "$mise_data_dir_override" ]]; then - export MISE_DATA_DIR="$mise_data_dir_override" -fi -# Path is resolved from this hook at runtime. -# shellcheck disable=SC1091 -. "${PLUGIN_ROOT}/lib/mise.sh" - -configured_version="${BUILDKITE_PLUGIN_GITHUB_ACTIONS_VERSION:-}" -source_ref="${BUILDKITE_PLUGIN_GITHUB_ACTIONS_SOURCE_REF:-}" -minimum_release_age="${BUILDKITE_PLUGIN_GITHUB_ACTIONS_MINIMUM_RELEASE_AGE:-0s}" - -if [[ -n "$source_ref" && -n "$configured_version" ]]; then - echo "github-actions plugin: version and source-ref are mutually exclusive" >&2 - exit 1 -fi -if [[ -n "$source_ref" && ! "$source_ref" =~ ^[0-9a-f]{40}$ ]] && - ! git check-ref-format "refs/heads/$source_ref"; then - echo "github-actions plugin: invalid buildkite-gha source ref '$source_ref'; expected a full lowercase 40-character commit, branch, or tag" >&2 - exit 1 -fi +error() { echo "github-actions plugin: $*" >&2; exit 1; } +download() { + curl --disable --fail --silent --show-error --location --globoff \ + --proto '=https' --proto-redir '=https' --tlsv1.2 "$1" --output "$2" +} -configured_version="${configured_version:-latest}" -configured_version="${configured_version#v}" -if [[ "$configured_version" != latest ]]; then - if [[ "$configured_version" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]] && - (( BASH_REMATCH[1] > 0 || BASH_REMATCH[2] >= 9 )); then - : - else - echo "github-actions plugin: invalid CLI version '$configured_version'; expected latest or an exact stable release from 0.9.0 onward" >&2 - exit 1 +command -v jq >/dev/null || error "required command 'jq' was not found" +configuration="${BUILDKITE_PLUGIN_CONFIGURATION:?BUILDKITE_PLUGIN_CONFIGURATION is required}" +jq -e -s 'length == 1 and (.[0] | type == "object")' <<< "$configuration" >/dev/null || error "configuration must be a JSON object" +for retired in source-ref minimum-release-age; do + if jq -e --arg key "$retired" 'has($key)' <<< "$configuration" >/dev/null; then + error "'$retired' is no longer supported; use version or binary" fi +done +if jq -e 'has("version") and has("binary")' <<< "$configuration" >/dev/null; then + error "version and binary are mutually exclusive" fi host_os="$(uname -s)" host_arch="$(uname -m)" case "$host_os/$host_arch" in - Linux/x86_64|Linux/amd64) - host_platform=linux/amd64 - ;; - Darwin/arm64|Darwin/aarch64) - host_platform=darwin/arm64 - ;; - *) - echo "github-actions plugin: unsupported importer platform '$host_os/$host_arch'; only Linux amd64 and Darwin arm64 are supported" >&2 - exit 1 - ;; + Linux/x86_64|Linux/amd64) platform=linux/amd64; asset=buildkite-gha_Linux_x86_64.tar.gz ;; + Linux/arm64|Linux/aarch64) platform=linux/arm64; asset=buildkite-gha_Linux_arm64.tar.gz ;; + Darwin/arm64|Darwin/aarch64) platform=darwin/arm64; asset=buildkite-gha_Darwin_arm64.tar.gz ;; + *) error "unsupported importer platform '$host_os/$host_arch'; expected Linux amd64/arm64 or Darwin arm64" ;; esac -if [[ -n "$source_ref" && ! "$source_ref" =~ ^[0-9a-f]{40}$ ]]; then - if ! remote_refs="$(git ls-remote --exit-code https://github.com/buildkite/buildkite-gha.git \ - "refs/heads/$source_ref" "refs/tags/$source_ref" "refs/tags/$source_ref^{}")"; then - echo "github-actions plugin: could not resolve buildkite-gha source ref '$source_ref'" >&2 - exit 1 - fi - branch_commit='' tag_commit='' peeled_commit='' - while read -r commit ref; do - case "$ref" in - "refs/heads/$source_ref") branch_commit="$commit" ;; - "refs/tags/$source_ref") tag_commit="$commit" ;; - "refs/tags/$source_ref^{}") peeled_commit="$commit" ;; - esac - done <<< "$remote_refs" - if [[ -n "$branch_commit" && -n "$tag_commit" ]]; then - echo "github-actions plugin: source ref '$source_ref' matches both a branch and tag; use a full commit instead" >&2 - exit 1 - fi - resolved_commit="${branch_commit:-${peeled_commit:-$tag_commit}}" - if [[ ! "$resolved_commit" =~ ^[0-9a-f]{40}$ ]]; then - echo "github-actions plugin: source ref '$source_ref' did not resolve to a full commit" >&2 - exit 1 - fi - echo "github-actions plugin: resolved source ref '$source_ref' to $resolved_commit" >&2 - source_ref="$resolved_commit" +binary_url='' +if jq -e 'has("binary")' <<< "$configuration" >/dev/null; then + jq -e ' + def url: type == "string" and test("^https://[^/@\\s?#]+([/?][^\\s#]*)?(#sha256=[0-9a-fA-F]{64})?$") and (test("[\u0000-\u0020\u007f]") | not); + .binary | if type == "object" then + length > 0 and all(to_entries[]; + (.key == "linux/amd64" or .key == "linux/arm64" or .key == "darwin/arm64" or .key == "windows/amd64") and (.value | url)) + else url end + ' <<< "$configuration" >/dev/null || error "binary must be an HTTPS executable URL or a map of supported platforms to HTTPS executable URLs" + binary_url="$(jq -r --arg platform "$platform" '.binary | if type == "string" then . else .[$platform] // empty end' <<< "$configuration")" + [[ -n "$binary_url" ]] || error "binary has no URL for importer platform '$platform'" +else + version="$(jq -er 'if has("version") then .version | select(type == "string") | select(test("[\u0000-\u0020\u007f]") | not) else "latest" end' <<< "$configuration")" || error "version must be latest or an exact release tag" + # SemVer identifiers, including prereleases and build metadata; no source refs. + number='(0|[1-9][0-9]*)' + identifier='(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)' + release_pattern="^v?$number\.$number\.$number(-$identifier(\.$identifier)*)?(\+[0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*)?$" + [[ "$version" == latest || "$version" =~ $release_pattern ]] || error "version must be latest or an exact release tag" fi -echo "--- :github: Prepare workflows" - -setup_mise "$host_platform" -export MISE_YES=1 -if [[ -n "$source_ref" ]]; then - runtime_dir="$(mktemp -d /tmp/buildkite-gha-source.XXXXXXXXXX)" - trap 'rm -rf -- "$runtime_dir"' EXIT - source_gopath="$runtime_dir/go" - source_mod_cache="$MISE_DATA_DIR/github-actions-buildkite-plugin/go-mod-cache" - - for target in linux/amd64:LINUX darwin/arm64:DARWIN windows/amd64:WINDOWS; do - platform="${target%:*}" - runtime_environment="BUILDKITE_GHA_PLUGIN_DEV_${target##*:}_RUNTIME" - echo "github-actions plugin: building $platform runtime from buildkite-gha source commit $source_ref with Go 1.26.10" >&2 - "$MISE_BIN" --no-config exec go@1.26.10 -- \ - env -u GOBIN CGO_ENABLED=0 GOTOOLCHAIN=local GOOS="${platform%/*}" GOARCH="${platform#*/}" GOPATH="$source_gopath" GOMODCACHE="$source_mod_cache" \ - go install -trimpath "github.com/buildkite/buildkite-gha/cmd/buildkite-gha@${source_ref}" +runtime_dir="$(mktemp -d "${TMPDIR:-/tmp}/buildkite-gha.XXXXXXXXXX")" +trap 'rm -rf -- "$runtime_dir"' EXIT +trap 'exit 130' INT +trap 'exit 143' TERM - runtime_path="$source_gopath/bin" - if [[ "$platform" != "$host_platform" ]]; then - runtime_path+="/${platform/\//_}" +echo "--- :github: Prepare workflows" +if [[ -n "$binary_url" ]]; then + download "${binary_url%%#*}" "$runtime_dir/buildkite-gha" + if [[ "$binary_url" == *'#'* ]]; then + checksum="$(printf '%s' "${binary_url##*#sha256=}" | tr 'A-F' 'a-f')" + if [[ "$platform" == linux/* ]]; then + actual="$(sha256sum "$runtime_dir/buildkite-gha")" + else + actual="$(shasum -a 256 "$runtime_dir/buildkite-gha")" fi - runtime_path+="/buildkite-gha" - if [[ "$platform" == windows/* ]]; then - runtime_path+=".exe" + [[ "${actual%% *}" == "$checksum" ]] || error "binary checksum verification failed" + fi +else + if [[ "$version" == latest ]]; then + download https://api.github.com/repos/buildkite/buildkite-gha/releases/latest "$runtime_dir/release.json" + version="$(jq -er '.tag_name | select(type == "string") | select(test("[\u0000-\u0020\u007f]") | not)' "$runtime_dir/release.json")" || error "latest release response has no valid tag_name" + [[ "$version" =~ $release_pattern ]] || error "latest release response has an invalid tag_name" + else + version="v${version#v}" + fi + release_url="https://github.com/buildkite/buildkite-gha/releases/download/$version" + download "$release_url/$asset" "$runtime_dir/archive.tar.gz" + download "$release_url/checksums.txt" "$runtime_dir/checksums.txt" + checksum='' + while read -r hash filename extra; do + if [[ "$filename" == "$asset" && -z "$extra" ]]; then + [[ -z "$checksum" && "$hash" =~ ^[0-9a-fA-F]{64}$ ]] || error "invalid or duplicate release checksum" + checksum="$(printf '%s' "$hash" | tr 'A-F' 'a-f')" fi - export "$runtime_environment=$runtime_path" - done + done < "$runtime_dir/checksums.txt" + [[ -n "$checksum" ]] || error "release checksum is missing for $asset" + if [[ "$platform" == linux/* ]]; then + actual="$(sha256sum "$runtime_dir/archive.tar.gz")" + else + actual="$(shasum -a 256 "$runtime_dir/archive.tar.gz")" + fi + [[ "${actual%% *}" == "$checksum" ]] || error "release archive checksum verification failed" - echo "github-actions plugin: running native $host_platform importer" >&2 - "$source_gopath/bin/buildkite-gha" plugin -else - export MISE_GITHUB_GH_CLI_TOKENS=false - export MISE_MINIMUM_RELEASE_AGE="$minimum_release_age" - export MISE_QUIET=1 - exec "$MISE_BIN" --no-config exec \ - "github:buildkite/buildkite-gha@${configured_version}" \ - -- buildkite-gha plugin + # Never extract archive paths to disk. Require one regular executable member, + # then stream only its contents into our private directory. + unset TAR_OPTIONS GZIP + member="$(tar -tvzf "$runtime_dir/archive.tar.gz" -- buildkite-gha)" + [[ "$member" == -* && "$member" != *$'\n'* ]] || error "release archive must contain one regular buildkite-gha executable" + tar -xOzf "$runtime_dir/archive.tar.gz" -- buildkite-gha > "$runtime_dir/buildkite-gha" fi +[[ -s "$runtime_dir/buildkite-gha" ]] || error "downloaded executable is empty" +chmod 0700 "$runtime_dir/buildkite-gha" +"$runtime_dir/buildkite-gha" plugin diff --git a/lib/mise.sh b/lib/mise.sh deleted file mode 100755 index dc9771c..0000000 --- a/lib/mise.sh +++ /dev/null @@ -1,122 +0,0 @@ -#!/usr/bin/env bash - -# Qualified release with rustls-webpki 0.103.16 and webpki-root-certs 1.0.10. -MISE_BOOTSTRAP_VERSION="2026.10.17" -MISE_MINIMUM_VERSION="2026.10.17" - -mise_error() { echo "github-actions plugin: $*" >&2; } - -mise_version() { - local output - output="$("$1" version 2>/dev/null)" || return 1 - if [[ "$output" =~ v?([0-9]{4})\.([0-9]+)\.([0-9]+) ]]; then - printf '%s.%s.%s\n' "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}" "${BASH_REMATCH[3]}" - return - fi - return 1 -} - -mise_is_compatible() { - local version year month patch - version="$(mise_version "$1")" || return 1 - IFS=. read -r year month patch <<< "$version" - (( year > 2026 || - (year == 2026 && month > 10) || - (year == 2026 && month == 10 && patch >= 17) )) -} - -mise_data_dir() { - if [[ -n "${MISE_DATA_DIR:-}" ]]; then - printf '%s\n' "$MISE_DATA_DIR" - elif [[ "${BUILDKITE_COMPUTE_TYPE:-self-hosted}" == hosted && -d /cache/bkcache && -w /cache/bkcache ]]; then - printf '%s\n' /cache/bkcache/mise - elif [[ -n "${BUILDKITE_AGENT_DATA_PATH:-}" ]]; then - printf '%s\n' "${BUILDKITE_AGENT_DATA_PATH}/cache/mise" - elif [[ -n "${XDG_DATA_HOME:-}" ]]; then - printf '%s\n' "${XDG_DATA_HOME}/mise" - elif [[ -n "${HOME:-}" ]]; then - printf '%s\n' "${HOME}/.local/share/mise" - else - mise_error "HOME, XDG_DATA_HOME, MISE_DATA_DIR, or BUILDKITE_AGENT_DATA_PATH is required to install mise" - return 1 - fi -} - -install_mise() ( - local platform="$1" destination_dir archive extracted staged asset checksum url checksum_output actual_checksum - for command in curl tar mktemp cp; do - command -v "$command" >/dev/null 2>&1 || { - mise_error "required command '$command' was not found" - return 1 - } - done - case "$platform" in - linux/amd64) - asset="mise-v${MISE_BOOTSTRAP_VERSION}-linux-x64-musl.tar.gz" - checksum="54dc5f1a0de14bddf70a4d8273603647c140eedd8b9d083a8f73ca13a9a82f53" - command -v sha256sum >/dev/null 2>&1 || { mise_error "required command 'sha256sum' was not found"; return 1; } - ;; - darwin/arm64) - asset="mise-v${MISE_BOOTSTRAP_VERSION}-macos-arm64.tar.gz" - checksum="d7c50c1683524a764260986ccc9c41c0de5e1fcd491714b5640b657b76781af6" - command -v shasum >/dev/null 2>&1 || { mise_error "required command 'shasum' was not found"; return 1; } - ;; - *) mise_error "unsupported mise bootstrap platform '$platform'"; return 1 ;; - esac - - destination_dir="$(dirname "$MISE_BIN")" - mkdir -p "$destination_dir" - archive="$(mktemp "${TMPDIR:-/tmp}/mise.tar.gz.XXXXXX")" - extracted="$(mktemp -d "${TMPDIR:-/tmp}/mise.XXXXXX")" - staged="$(mktemp "${destination_dir}/.mise.XXXXXX")" - trap 'rm -rf -- "$archive" "$extracted" "$staged"' EXIT - - url="https://github.com/jdx/mise/releases/download/v${MISE_BOOTSTRAP_VERSION}/${asset}" - echo "github-actions plugin: installing mise ${MISE_BOOTSTRAP_VERSION}" >&2 - curl --disable --fail --silent --show-error --location \ - --proto '=https' --proto-redir '=https' --tlsv1.2 \ - "$url" --output "$archive" - if [[ "$platform" == linux/amd64 ]]; then - checksum_output="$(sha256sum "$archive")" - else - checksum_output="$(shasum -a 256 "$archive")" - fi - actual_checksum="${checksum_output%% *}" - [[ "$actual_checksum" == "$checksum" ]] || { - mise_error "mise release archive checksum verification failed" - return 1 - } - - unset TAR_OPTIONS GZIP - tar -xozf "$archive" -C "$extracted" - [[ -f "$extracted/mise/bin/mise" && ! -L "$extracted/mise/bin/mise" ]] || { - mise_error "mise release archive has an unexpected layout" - return 1 - } - cp -- "$extracted/mise/bin/mise" "$staged" - chmod 0755 "$staged" - mv -f -- "$staged" "$MISE_BIN" - [[ "$(mise_version "$MISE_BIN")" == "$MISE_BOOTSTRAP_VERSION" ]] || { - mise_error "installed mise reported an unexpected version" - return 1 - } -) - -setup_mise() { - local platform="$1" candidate data_dir platform_dir - data_dir="$(mise_data_dir)" - export MISE_DATA_DIR="$data_dir" - if candidate="$(command -v mise 2>/dev/null)" && mise_is_compatible "$candidate"; then - MISE_BIN="$candidate" - return - fi - - if [[ -n "${candidate:-}" ]]; then - mise_error "mise on PATH is older than ${MISE_MINIMUM_VERSION}; using the managed version" - fi - platform_dir="${platform//\//-}" - MISE_BIN="${data_dir}/github-actions-buildkite-plugin/mise/${MISE_BOOTSTRAP_VERSION}/${platform_dir}/mise" - if ! mise_is_compatible "$MISE_BIN"; then - install_mise "$platform" - fi -} diff --git a/mise.toml b/mise.toml index 0bcf085..de9328f 100644 --- a/mise.toml +++ b/mise.toml @@ -12,7 +12,7 @@ run = "bats tests" [tasks.lint] description = "Run ShellCheck" -run = "shellcheck hooks/* lib/*" +run = "shellcheck hooks/*" [tasks.check] description = "Run all local checks" diff --git a/plugin.yml b/plugin.yml index 39dbd30..5bcdb80 100644 --- a/plugin.yml +++ b/plugin.yml @@ -4,73 +4,33 @@ author: https://github.com/buildkite-plugins requirements: - bash - curl - - git + - jq - tar - sha256sum (Linux) or shasum (macOS) - mktemp - - cp configuration: type: object - oneOf: - - not: - anyOf: - - required: - - workflow - - required: - - workflows - - required: - - workflow - - required: - - workflows + not: + anyOf: + - required: [version, binary] + - required: [source-ref] + - required: [minimum-release-age] properties: - workflow: - type: string - minLength: 1 - pattern: "^[^*?\\[\\]{}]+\\.ya?ml$" - description: One explicit .yml or .yaml workflow path. Missing or untracked paths are skipped. - workflows: - type: array - minItems: 1 - items: - type: string - minLength: 1 - pattern: "^[^*?\\[\\]{}]+\\.ya?ml$" - description: Explicit .yml or .yaml workflow paths. Missing or untracked paths are skipped. version: type: string default: latest - pattern: "^(latest|v?(0\\.(9|[1-9][0-9]+)|[1-9][0-9]*\\.(0|[1-9][0-9]*))\\.(0|[1-9][0-9]*))$" - description: Latest stable or an exact stable buildkite-gha CLI release from 0.9.0 onward. - source-ref: - type: string - minLength: 1 - description: Buildkite-gha commit, branch, or tag to build for development testing. Symbolic refs resolve once to a commit. Mutually exclusive with version. - minimum-release-age: - type: string - minLength: 1 - default: 0s - description: Minimum age passed to mise when selecting the latest buildkite-gha release. - experimental-runner-user: - type: boolean - default: true - description: Run generated Linux jobs as a dedicated runner user. Set to false only as a temporary compatibility opt-out. - oidc: - type: object - properties: - claims: - type: array - items: - type: string - minLength: 1 - description: Optional claim names to include in Buildkite OIDC tokens. - aws-session-tags: - type: array - items: - type: string - minLength: 1 - description: Claim names to duplicate into AWS session-tag format. - subject-claim: - type: string - minLength: 1 - description: Immutable claim name to use as the token subject. + pattern: '^(latest|v?(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\+[0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*)?)$' + description: Latest stable or an exact buildkite-gha release, including prereleases. + binary: + description: HTTPS raw executable URL with optional SHA256 fragment, or URLs keyed by platform. Requires companion CLI support. + oneOf: + - type: string + pattern: '^https://[^/@\s?#]+([/?][^\s#]*)?(#sha256=[0-9a-fA-F]{64})?$' + - type: object + minProperties: 1 + patternProperties: + '^(linux/(amd64|arm64)|darwin/arm64|windows/amd64)$': + type: string + pattern: '^https://[^/@\s?#]+([/?][^\s#]*)?(#sha256=[0-9a-fA-F]{64})?$' + additionalProperties: false additionalProperties: true diff --git a/tests/command.bats b/tests/command.bats index c83b47d..924bd59 100755 --- a/tests/command.bats +++ b/tests/command.bats @@ -2,581 +2,268 @@ setup() { REPO="$BATS_TEST_DIRNAME/.." - TMP="$(mktemp -d)" - export MOCK_LOG="$TMP/mock.log" - export BUILDKITE_PLUGIN_CONFIGURATION='{"workflow":".github/workflows/ci.yml","runners":[{"runs-on":"ubuntu-latest","queue":"hosted"}]}' - export BUILDKITE_COMMIT=1111111111111111111111111111111111111111 - unset BUILDKITE_PLUGIN_GITHUB_ACTIONS_VERSION BUILDKITE_PLUGIN__VERSION - unset BUILDKITE_PLUGIN_GITHUB_ACTIONS_SOURCE_REF - unset BUILDKITE_PLUGIN_GITHUB_ACTIONS_MINIMUM_RELEASE_AGE BUILDKITE_PLUGIN__MINIMUM_RELEASE_AGE - unset MISE_DATA_DIR - export MOCK_HOST_PLATFORM=linux/amd64 - mkdir -p "$TMP/bin" + export FIXTURE="$BATS_TEST_TMPDIR/fixture" + export TMPDIR="$BATS_TEST_TMPDIR/downloads" + export MOCK_LOG="$BATS_TEST_TMPDIR/download.log" + export BUILDKITE_PLUGIN_CONFIGURATION='{}' + export MOCK_OS=Linux MOCK_ARCH=x86_64 + mkdir -p "$FIXTURE/bin" "$TMPDIR" : > "$MOCK_LOG" - cat > "$TMP/buildkite-gha" <<'EOF' + cat > "$FIXTURE/buildkite-gha" <<'EOF' #!/usr/bin/env bash -set -euo pipefail -printf 'runtime=%s %s\n' "$0" "$*" >> "${MOCK_LOG:?}" -printf 'runtime-configuration=%s\n' "${BUILDKITE_PLUGIN_CONFIGURATION:-}" >> "${MOCK_LOG:?}" -printf 'darwin-runtime=%s\n' "${BUILDKITE_GHA_PLUGIN_DEV_DARWIN_RUNTIME:-}" >> "${MOCK_LOG:?}" -printf 'linux-runtime=%s\n' "${BUILDKITE_GHA_PLUGIN_DEV_LINUX_RUNTIME:-}" >> "${MOCK_LOG:?}" -printf 'windows-runtime=%s\n' "${BUILDKITE_GHA_PLUGIN_DEV_WINDOWS_RUNTIME:-}" >> "${MOCK_LOG:?}" -printf 'runtime-root-mode=%s\n' "$(stat -c %a "$(dirname "$(dirname "$0")")")" >> "${MOCK_LOG:?}" -[[ "${1:-}" == plugin ]] -[[ "${BUILDKITE_GHA_PLUGIN_DEV_WINDOWS_RUNTIME:-}" == /*/go/bin/windows_amd64/buildkite-gha.exe ]] -[[ -x "$BUILDKITE_GHA_PLUGIN_DEV_WINDOWS_RUNTIME" ]] -if [[ "${MOCK_HOST_PLATFORM:?}" == linux/amd64 ]]; then - [[ "${BUILDKITE_GHA_PLUGIN_DEV_LINUX_RUNTIME:-}" == "$0" ]] - [[ "${BUILDKITE_GHA_PLUGIN_DEV_DARWIN_RUNTIME:-}" == /*/go/bin/darwin_arm64/buildkite-gha ]] - [[ -x "$BUILDKITE_GHA_PLUGIN_DEV_DARWIN_RUNTIME" ]] -else - [[ "${BUILDKITE_GHA_PLUGIN_DEV_DARWIN_RUNTIME:-}" == "$0" ]] - [[ "${BUILDKITE_GHA_PLUGIN_DEV_LINUX_RUNTIME:-}" == /*/go/bin/linux_amd64/buildkite-gha ]] - [[ -x "$BUILDKITE_GHA_PLUGIN_DEV_LINUX_RUNTIME" ]] -fi +set -eu +printf '%s\n' "$*" > "$FIXTURE/arguments" +printf '%s' "$BUILDKITE_PLUGIN_CONFIGURATION" > "$FIXTURE/configuration" +stat -c %a "$(dirname "$0")" > "$FIXTURE/mode" 2>/dev/null || stat -f %Lp "$(dirname "$0")" > "$FIXTURE/mode" +ls -A "$(dirname "$0")" > "$FIXTURE/files" exit "${MOCK_IMPORTER_EXIT:-0}" EOF - chmod +x "$TMP/buildkite-gha" - export MOCK_RUNTIME_TEMPLATE="$TMP/buildkite-gha" - write_mise "$TMP/bin/mise" 2026.10.17 - export PATH="$TMP/bin:$PATH" -} - -write_mise() { - local path="$1" version="$2" - cat > "$path" <> "\${MOCK_LOG:?}" -printf 'configuration=%s\n' "\${BUILDKITE_PLUGIN_CONFIGURATION:-}" >> "\${MOCK_LOG:?}" -printf 'commit=%s\n' "\${BUILDKITE_COMMIT:-}" >> "\${MOCK_LOG:?}" -printf 'group=%s\n' "\${BUILDKITE_GROUP_LABEL:-}" >> "\${MOCK_LOG:?}" -printf 'minimum-release-age=%s\n' "\${MISE_MINIMUM_RELEASE_AGE:-}" >> "\${MOCK_LOG:?}" -printf 'github-cli-tokens=%s\n' "\${MISE_GITHUB_GH_CLI_TOKENS:-}" >> "\${MOCK_LOG:?}" -printf 'yes=%s\n' "\${MISE_YES:-}" >> "\${MOCK_LOG:?}" -printf 'quiet=%s\n' "\${MISE_QUIET:-}" >> "\${MOCK_LOG:?}" -printf 'cwd=%s\n' "\$PWD" >> "\${MOCK_LOG:?}" -printf 'prereleases=%s\n' "\${MISE_PRERELEASES:-}" >> "\${MOCK_LOG:?}" -printf 'url-replacements=%s\n' "\${MISE_URL_REPLACEMENTS:-}" >> "\${MOCK_LOG:?}" -printf 'installs-dir=%s\n' "\${MISE_INSTALLS_DIR:-}" >> "\${MOCK_LOG:?}" -printf 'credential-command=%s\n' "\${MISE_GITHUB_CREDENTIAL_COMMAND:-}" >> "\${MOCK_LOG:?}" -if [[ "\${1:-}" == --no-config && "\${2:-}" == exec && "\${4:-}" == -- && "\${5:-}" == buildkite-gha && "\${6:-}" == plugin ]]; then - if [[ "\${MISE_QUIET:-}" != 1 ]]; then - echo 'mise github:buildkite/buildkite-gha installing' - fi - if [[ "\${MOCK_MISE_ACQUISITION_FAILURE:-}" == 1 ]]; then - echo 'mise ERROR failed to download buildkite-gha release' >&2 - exit 42 - fi - if [[ -z "\${BUILDKITE_PLUGIN_CONFIGURATION:-}" ]]; then - echo 'buildkite-gha: plugin: BUILDKITE_PLUGIN_CONFIGURATION is required' >&2 - exit 2 - fi - if [[ -n "\${MOCK_IMPORTER_ERROR:-}" ]]; then - echo "\$MOCK_IMPORTER_ERROR" >&2 - fi - exit "\${MOCK_IMPORTER_EXIT:-0}" -fi -if [[ "\${1:-}" == --no-config && "\${2:-}" == exec && "\${3:-}" == go@1.26.10 && "\${4:-}" == -- && "\${5:-}" == env && "\${6:-}" == -u && "\${7:-}" == GOBIN && "\${8:-}" == CGO_ENABLED=0 && "\${9:-}" == GOTOOLCHAIN=local ]]; then - goos="\${10#GOOS=}" - goarch="\${11#GOARCH=}" - gopath="\${12#GOPATH=}" - gomodcache="\${13#GOMODCACHE=}" - if [[ "\${14:-}" == go && "\${15:-}" == install && "\${16:-}" == -trimpath && -n "\$goos" && -n "\$goarch" && -n "\$gopath" && -n "\$gomodcache" ]]; then - printf 'build=%s/%s:%s:%s:%s\n' "\$goos" "\$goarch" "\$gopath" "\$gomodcache" "\${17:-}" >> "\${MOCK_LOG:?}" - if [[ "\${MOCK_BUILD_FAILURE_PLATFORM:-}" == "\$goos/\$goarch" ]]; then - exit 42 - fi - gobin="\$gopath/bin" - if [[ "\$goos/\$goarch" != "\${MOCK_HOST_PLATFORM:?}" ]]; then - gobin="\$gobin/\${goos}_\${goarch}" - fi - executable=buildkite-gha - if [[ "\$goos" == windows ]]; then - executable=buildkite-gha.exe - fi - mkdir -p "\$gobin" - cp "\${MOCK_RUNTIME_TEMPLATE:?}" "\$gobin/\$executable" - chmod +x "\$gobin/\$executable" - exit - fi -fi -exit 64 -EOF - chmod +x "$path" -} - -prepare_bootstrap() { - local payload="$TMP/payload" - rm -f "$TMP/bin/mise" - mkdir -p "$payload/mise/bin" - write_mise "$payload/mise/bin/mise" 2026.10.17 - tar -czf "$TMP/mise.tar.gz" -C "$payload" mise - - cat > "$TMP/bin/curl" <<'EOF' + echo 'not extracted' > "$FIXTURE/LICENSE" + tar -czf "$FIXTURE/archive.tar.gz" -C "$FIXTURE" buildkite-gha LICENSE + checksums + echo '{"tag_name":"v0.107.1"}' > "$FIXTURE/release.json" + cat > "$FIXTURE/bin/curl" <<'EOF' #!/usr/bin/env bash set -eu -printf 'curl=%s\n' "$*" >> "${MOCK_LOG:?}" -out="" -while [[ $# -gt 0 ]]; do - case "$1" in - --output) out="$2"; shift 2 ;; - *) shift ;; - esac -done -cp "${MOCK_MISE_ARCHIVE:?}" "$out" -EOF - cat > "$TMP/bin/sha256sum" <<'EOF' -#!/usr/bin/env bash -printf 'sha256sum=%s\n' "$*" >> "${MOCK_LOG:?}" -[[ "${MOCK_CHECKSUM_FAILURE:-}" != 1 ]] || exit 1 -printf '%s %s\n' "${MOCK_MISE_SHA256:?}" "$1" -EOF - cat > "$TMP/bin/shasum" <<'EOF' -#!/usr/bin/env bash -printf 'shasum=%s\n' "$*" >> "${MOCK_LOG:?}" -[[ "$1" == -a && "$2" == 256 ]] -printf '%s %s\n' "${MOCK_MISE_SHA256:?}" "$3" -EOF - chmod +x "$TMP/bin/curl" "$TMP/bin/sha256sum" "$TMP/bin/shasum" - export MOCK_MISE_ARCHIVE="$TMP/mise.tar.gz" - export MOCK_MISE_SHA256=54dc5f1a0de14bddf70a4d8273603647c140eedd8b9d083a8f73ca13a9a82f53 - export MISE_DATA_DIR="$TMP/mise-data" - export PATH="$TMP/bin:/usr/bin:/bin" -} - -mock_host() { - export MOCK_HOST_PLATFORM="$1" - cat > "$TMP/bin/uname" <> "$MOCK_LOG" +if [[ -n "${MOCK_FAIL_SUFFIX:-}" && "$url" == *"$MOCK_FAIL_SUFFIX" ]]; then + echo 'curl: fixture HTTP failure' >&2 + exit 22 fi +case "$url" in + https://api.github.com/repos/buildkite/buildkite-gha/releases/latest) source=release.json ;; + https://github.com/buildkite/buildkite-gha/releases/download/*/checksums.txt) source=checksums.txt ;; + https://github.com/buildkite/buildkite-gha/releases/download/*/buildkite-gha_*.tar.gz) source=archive.tar.gz ;; + https://example.com/*) source=buildkite-gha ;; + *) exit 64 ;; +esac +cp "$FIXTURE/$source" "${14}" EOF - chmod +x "$TMP/bin/uname" -} - -mock_source_resolution() { - export REAL_GIT="$(command -v git)" - cat > "$TMP/bin/git" <<'EOF' + cat > "$FIXTURE/bin/uname" <<'EOF' #!/usr/bin/env bash -set -euo pipefail -if [[ "$1" == check-ref-format ]]; then - exec "$REAL_GIT" "$@" -fi -[[ "$1" == ls-remote && "$2" == --exit-code && "$3" == https://github.com/buildkite/buildkite-gha.git ]] -printf 'resolve=%s\n' "$*" >> "${MOCK_LOG:?}" -printf '%s\n' "${MOCK_SOURCE_REFS:-}" -exit "${MOCK_RESOLUTION_EXIT:-0}" +if [[ "$1" == -s ]]; then echo "$MOCK_OS"; else echo "$MOCK_ARCH"; fi EOF - chmod +x "$TMP/bin/git" + chmod +x "$FIXTURE/bin/"* + export PATH="$FIXTURE/bin:$PATH" } -teardown() { rm -rf "$TMP"; } - -@test "uses mise from PATH to select latest and invoke the hidden plugin command" { - mock_source_resolution - run "$REPO/hooks/command" - [ "$status" -eq 0 ] || { echo "$output"; false; } - [ "$(grep -c '^resolve=' "$MOCK_LOG")" -eq 0 ] - [[ "$output" == *"--- :github: Prepare workflows"* ]] - grep -Fx 'mise=--no-config exec github:buildkite/buildkite-gha@latest -- buildkite-gha plugin' "$MOCK_LOG" - grep -Fx 'minimum-release-age=0s' "$MOCK_LOG" - grep -Fx 'github-cli-tokens=false' "$MOCK_LOG" - grep -Fx 'yes=1' "$MOCK_LOG" - grep -Fx 'quiet=1' "$MOCK_LOG" - [[ "$output" != *"mise github:buildkite/buildkite-gha installing"* ]] - grep -Fx "cwd=$PWD" "$MOCK_LOG" +checksums() { + local hash + hash="$(shasum -a 256 "$FIXTURE/archive.tar.gz")" + for asset in Linux_x86_64 Linux_arm64 Darwin_arm64; do + printf '%s buildkite-gha_%s.tar.gz\n' "${hash%% *}" "$asset" + done > "$FIXTURE/checksums.txt" } -@test "passes an empty configuration object to buildkite-gha unchanged" { - export BUILDKITE_PLUGIN_CONFIGURATION='{}' - run "$REPO/hooks/command" - [ "$status" -eq 0 ] || { echo "$output"; false; } - grep -Fx 'mise=--no-config exec github:buildkite/buildkite-gha@latest -- buildkite-gha plugin' "$MOCK_LOG" - grep -Fx 'configuration={}' "$MOCK_LOG" +assert_clean() { + [ -z "$(ls -A "$TMPDIR")" ] } -@test "passes selector-free runtime and behavioral options to buildkite-gha unchanged" { - export BUILDKITE_PLUGIN_GITHUB_ACTIONS_VERSION=v0.35.1 - export BUILDKITE_PLUGIN_GITHUB_ACTIONS_MINIMUM_RELEASE_AGE=24h - export BUILDKITE_PLUGIN_CONFIGURATION='{"version":"v0.35.1","minimum-release-age":"24h","experimental-runner-user":false,"runners":[{"runs-on":"ubuntu-latest","queue":"hosted"}],"oidc":{"claims":["organization_id"],"subject-claim":"pipeline_id"}}' +@test "resolves latest once, verifies the release, and extracts only the executable privately" { + export TAR_OPTIONS='--checkpoint-action=exec=false' run "$REPO/hooks/command" [ "$status" -eq 0 ] || { echo "$output"; false; } - grep -Fx 'mise=--no-config exec github:buildkite/buildkite-gha@0.35.1 -- buildkite-gha plugin' "$MOCK_LOG" - grep -Fx 'minimum-release-age=24h' "$MOCK_LOG" - grep -Fx "configuration=$BUILDKITE_PLUGIN_CONFIGURATION" "$MOCK_LOG" -} - -@test "leaves ordinary selector-free failure to buildkite-gha" { - export BUILDKITE_PLUGIN_GITHUB_ACTIONS_VERSION=v0.35.1 - export BUILDKITE_PLUGIN_CONFIGURATION='{"version":"v0.35.1","runners":[{"runs-on":"ubuntu-latest","queue":"hosted"}]}' - export MOCK_IMPORTER_EXIT=2 - export MOCK_IMPORTER_ERROR='buildkite-gha: plugin: workflow or workflows is required outside a GitHub Actions Pipeline Trigger build' - run "$REPO/hooks/command" - [ "$status" -eq 2 ] - [[ "$output" == *"$MOCK_IMPORTER_ERROR"* ]] - grep -Fx "configuration=$BUILDKITE_PLUGIN_CONFIGURATION" "$MOCK_LOG" -} - -@test "keeps mise acquisition failures visible in quiet mode" { - export MOCK_MISE_ACQUISITION_FAILURE=1 - run "$REPO/hooks/command" - [ "$status" -eq 42 ] - [[ "$output" == *"mise ERROR failed to download buildkite-gha release"* ]] - grep -Fx 'quiet=1' "$MOCK_LOG" -} - -@test "passes exact versions and a configured minimum release age to mise" { - export BUILDKITE_PLUGIN_GITHUB_ACTIONS_VERSION=v0.9.0 - export BUILDKITE_PLUGIN_GITHUB_ACTIONS_MINIMUM_RELEASE_AGE=24h - run "$REPO/hooks/command" - [ "$status" -eq 0 ] || { echo "$output"; false; } - grep -Fx 'mise=--no-config exec github:buildkite/buildkite-gha@0.9.0 -- buildkite-gha plugin' "$MOCK_LOG" - grep -Fx 'minimum-release-age=24h' "$MOCK_LOG" -} - -@test "passes experimental-runner-user unchanged to a supported release" { - export BUILDKITE_PLUGIN_GITHUB_ACTIONS_VERSION=0.13.7 - export BUILDKITE_PLUGIN_CONFIGURATION='{"workflow":".github/workflows/ci.yml","experimental-runner-user":true}' - run "$REPO/hooks/command" - [ "$status" -eq 0 ] || { echo "$output"; false; } - grep -Fx 'mise=--no-config exec github:buildkite/buildkite-gha@0.13.7 -- buildkite-gha plugin' "$MOCK_LOG" - grep -Fx "configuration=$BUILDKITE_PLUGIN_CONFIGURATION" "$MOCK_LOG" -} - -@test "passes an explicit workflow path array to buildkite-gha unchanged" { - export BUILDKITE_PLUGIN_CONFIGURATION='{"workflows":[".github/workflows/ci.yml",".github/workflows/release.yml"],"runners":[{"runs-on":"ubuntu-latest","queue":"hosted"}]}' - run "$REPO/hooks/command" - [ "$status" -eq 0 ] || { echo "$output"; false; } - grep -Fx "configuration=$BUILDKITE_PLUGIN_CONFIGURATION" "$MOCK_LOG" -} - -@test "passes one explicit workflow path to buildkite-gha unchanged" { - export BUILDKITE_PLUGIN_CONFIGURATION='{"workflow":".github/workflows/ci.yml","future":{"nested":[true,3]}}' - run "$REPO/hooks/command" - [ "$status" -eq 0 ] || { echo "$output"; false; } - grep -Fx "configuration=$BUILDKITE_PLUGIN_CONFIGURATION" "$MOCK_LOG" + [ "$(wc -l < "$MOCK_LOG")" -eq 3 ] + [ "$(grep -c '/releases/latest$' "$MOCK_LOG")" -eq 1 ] + grep -Fx 'https://github.com/buildkite/buildkite-gha/releases/download/v0.107.1/buildkite-gha_Linux_x86_64.tar.gz' "$MOCK_LOG" + grep -Fx 'https://github.com/buildkite/buildkite-gha/releases/download/v0.107.1/checksums.txt' "$MOCK_LOG" + [ "$(cat "$FIXTURE/arguments")" = plugin ] + [ "$(cat "$FIXTURE/mode")" = 700 ] + ! grep -Fx LICENSE "$FIXTURE/files" + assert_clean +} + +@test "accepts exact version shorthand and prereleases without resolving latest" { + for version in 0.107.1 v0.107.1 v1.0.0-rc.1 1.0.0-rc.1+build.2; do + : > "$MOCK_LOG" + export BUILDKITE_PLUGIN_CONFIGURATION="{\"version\":\"$version\"}" + run "$REPO/hooks/command" + [ "$status" -eq 0 ] || { echo "$output"; false; } + [ "$(wc -l < "$MOCK_LOG")" -eq 2 ] + grep -Fx "https://github.com/buildkite/buildkite-gha/releases/download/v${version#v}/checksums.txt" "$MOCK_LOG" + assert_clean + done } -@test "passes OIDC configuration to buildkite-gha unchanged" { - export BUILDKITE_PLUGIN_CONFIGURATION='{"workflow":".github/workflows/deploy.yml","oidc":{"claims":["organization_id"],"aws-session-tags":["organization_slug","pipeline_id"],"subject-claim":"pipeline_id"}}' - run "$REPO/hooks/command" - [ "$status" -eq 0 ] || { echo "$output"; false; } - grep -Fx "configuration=$BUILDKITE_PLUGIN_CONFIGURATION" "$MOCK_LOG" +@test "selects Linux arm64 and native macOS arm64 release assets" { + for host in Linux:aarch64:Linux_arm64 Darwin:arm64:Darwin_arm64; do + IFS=: read -r MOCK_OS MOCK_ARCH asset <<< "$host" + : > "$MOCK_LOG" + run "$REPO/hooks/command" + [ "$status" -eq 0 ] || { echo "$output"; false; } + grep -Fx "https://github.com/buildkite/buildkite-gha/releases/download/v0.107.1/buildkite-gha_$asset.tar.gz" "$MOCK_LOG" + assert_clean + done } -@test "passes experimental-runner-user to the native source importer" { - mock_source_resolution - commit=abcdef0123456789abcdef0123456789abcdef01 - export BUILDKITE_PLUGIN_GITHUB_ACTIONS_SOURCE_REF="$commit" - export BUILDKITE_PLUGIN_CONFIGURATION='{"workflow":".github/workflows/ci.yml","experimental-runner-user":true}' +@test "passes configuration byte-for-byte and preserves runtime exit status" { + export BUILDKITE_PLUGIN_CONFIGURATION=' {"version":"0.107.1", "workflows":["ci.yml"],"runners":"CLI validates this","future":{"nested":[true,3]}} ' + export BUILDKITE_PLUGIN_GITHUB_ACTIONS_VERSION=ignored-flattened-value + export MOCK_IMPORTER_EXIT=37 run "$REPO/hooks/command" - [ "$status" -eq 0 ] || { echo "$output"; false; } - [ "$(grep -c '^resolve=' "$MOCK_LOG")" -eq 0 ] - [ "$(grep -c '^build=' "$MOCK_LOG")" -eq 3 ] - for platform in linux/amd64 darwin/arm64 windows/amd64; do - [[ "$output" == *"building $platform runtime from buildkite-gha source commit $commit with Go 1.26.10"* ]] - done - [[ "$output" == *"running native linux/amd64 importer"* ]] - grep -E "^build=linux/amd64:/[^:]+:/[^:]+:github.com/buildkite/buildkite-gha/cmd/buildkite-gha@$commit$" "$MOCK_LOG" - grep -E "^build=darwin/arm64:/[^:]+:/[^:]+:github.com/buildkite/buildkite-gha/cmd/buildkite-gha@$commit$" "$MOCK_LOG" - grep -E "^build=windows/amd64:/[^:]+:/[^:]+:github.com/buildkite/buildkite-gha/cmd/buildkite-gha@$commit$" "$MOCK_LOG" - grep -E '^runtime=/[^ ]+/go/bin/buildkite-gha plugin$' "$MOCK_LOG" - grep -Fx "runtime-configuration=$BUILDKITE_PLUGIN_CONFIGURATION" "$MOCK_LOG" - darwin_runtime="$(sed -n 's/^darwin-runtime=//p' "$MOCK_LOG")" - [[ "$darwin_runtime" == /*/go/bin/darwin_arm64/buildkite-gha ]] - grep -Fx 'runtime-root-mode=700' "$MOCK_LOG" - [ ! -e "$darwin_runtime" ] - windows_runtime="$(sed -n 's/^windows-runtime=//p' "$MOCK_LOG")" - [[ "$windows_runtime" == "${darwin_runtime%/darwin_arm64/*}/windows_amd64/buildkite-gha.exe" ]] - [ ! -e "$windows_runtime" ] - grep -Fx 'minimum-release-age=' "$MOCK_LOG" + [ "$status" -eq 37 ] + [ "$(cat "$FIXTURE/configuration")" = "$BUILDKITE_PLUGIN_CONFIGURATION" ] + assert_clean } -@test "builds all runtimes and runs the native Darwin importer with Linux and Windows paths" { - mock_host darwin/arm64 - commit=abcdef0123456789abcdef0123456789abcdef01 - export BUILDKITE_PLUGIN_GITHUB_ACTIONS_SOURCE_REF="$commit" +@test "downloads a raw executable without release lookup or archives" { + export BUILDKITE_PLUGIN_CONFIGURATION='{"binary":"https://example.com/custom?build=123"}' run "$REPO/hooks/command" [ "$status" -eq 0 ] || { echo "$output"; false; } - [[ "$output" == *"running native darwin/arm64 importer"* ]] - grep -E "^build=windows/amd64:/[^:]+:/[^:]+:github.com/buildkite/buildkite-gha/cmd/buildkite-gha@$commit$" "$MOCK_LOG" - grep -E '^runtime=/[^ ]+/go/bin/buildkite-gha plugin$' "$MOCK_LOG" - linux_runtime="$(sed -n 's/^linux-runtime=//p' "$MOCK_LOG")" - [[ "$linux_runtime" == /*/go/bin/linux_amd64/buildkite-gha ]] - [ ! -e "$linux_runtime" ] - windows_runtime="$(sed -n 's/^windows-runtime=//p' "$MOCK_LOG")" - [[ "$windows_runtime" == "${linux_runtime%/linux_amd64/*}/windows_amd64/buildkite-gha.exe" ]] - [ ! -e "$windows_runtime" ] + [ "$(cat "$MOCK_LOG")" = 'https://example.com/custom?build=123' ] + [ "$(cat "$FIXTURE/files")" = buildkite-gha ] + [ "$(cat "$FIXTURE/configuration")" = "$BUILDKITE_PLUGIN_CONFIGURATION" ] + assert_clean } -@test "stops and removes source runtimes when a cross-build fails" { - export BUILDKITE_PLUGIN_GITHUB_ACTIONS_SOURCE_REF=abcdef0123456789abcdef0123456789abcdef01 - for platform in darwin/arm64 windows/amd64; do +@test "downloads only the importer from the platform map and keeps counterpart URLs unchanged" { + export BUILDKITE_PLUGIN_CONFIGURATION='{"binary":{"linux/amd64":"https://example.com/linux","linux/arm64":"https://example.com/arm","darwin/arm64":"https://example.com/mac","windows/amd64":"https://example.com/windows.exe"}}' + for host in Linux:x86_64:linux Linux:arm64:arm Darwin:arm64:mac; do + IFS=: read -r MOCK_OS MOCK_ARCH name <<< "$host" : > "$MOCK_LOG" - export MOCK_BUILD_FAILURE_PLATFORM="$platform" - run "$REPO/hooks/command" - [ "$status" -eq 42 ] || { echo "$output"; false; } - ! grep -q '^runtime=' "$MOCK_LOG" - source_gopath="$(sed -n 's/^build=linux\/amd64:\([^:]*\):.*$/\1/p' "$MOCK_LOG")" - [ -n "$source_gopath" ] - [ ! -e "${source_gopath%/*}" ] - done -} - -@test "resolves a branch once and passes server-selected configuration without runner overrides" { - mock_source_resolution - commit=1234567890abcdef1234567890abcdef12345678 - export BUILDKITE_PLUGIN_GITHUB_ACTIONS_SOURCE_REF=feature/source-testing - export BUILDKITE_PLUGIN_CONFIGURATION='{"source-ref":"feature/source-testing"}' - export MOCK_SOURCE_REFS="$commit refs/heads/feature/source-testing" - for host in linux/amd64 darwin/arm64; do - : > "$MOCK_LOG" - mock_host "$host" run "$REPO/hooks/command" [ "$status" -eq 0 ] || { echo "$output"; false; } - [[ "$output" == *"resolved source ref 'feature/source-testing' to $commit"* ]] - [ "$(grep -c '^resolve=' "$MOCK_LOG")" -eq 1 ] - grep -Fx 'resolve=ls-remote --exit-code https://github.com/buildkite/buildkite-gha.git refs/heads/feature/source-testing refs/tags/feature/source-testing refs/tags/feature/source-testing^{}' "$MOCK_LOG" - [ "$(grep -c '^build=' "$MOCK_LOG")" -eq 3 ] - for platform in linux/amd64 darwin/arm64 windows/amd64; do - grep -E "^build=$platform:/[^:]+:/[^:]+:github.com/buildkite/buildkite-gha/cmd/buildkite-gha@$commit$" "$MOCK_LOG" - done - grep -Fx "runtime-configuration=$BUILDKITE_PLUGIN_CONFIGURATION" "$MOCK_LOG" + [ "$(cat "$MOCK_LOG")" = "https://example.com/$name" ] + [ "$(cat "$FIXTURE/configuration")" = "$BUILDKITE_PLUGIN_CONFIGURATION" ] + assert_clean done } -@test "resolves lightweight and annotated tags to the commit rather than the tag object" { - mock_source_resolution - commit=1234567890abcdef1234567890abcdef12345678 - export BUILDKITE_PLUGIN_GITHUB_ACTIONS_SOURCE_REF=v0.71.1 - for refs in "$commit refs/tags/v0.71.1" "abcdef0123456789abcdef0123456789abcdef01 refs/tags/v0.71.1 -$commit refs/tags/v0.71.1^{}"; do - : > "$MOCK_LOG" - export MOCK_SOURCE_REFS="$refs" - run "$REPO/hooks/command" - [ "$status" -eq 0 ] || { echo "$output"; false; } - [ "$(grep -c '^resolve=' "$MOCK_LOG")" -eq 1 ] - [ "$(grep -c '^build=' "$MOCK_LOG")" -eq 3 ] - [ "$(grep -c "^build=.*@$commit$" "$MOCK_LOG")" -eq 3 ] +@test "verifies raw executable checksums with either hex case before invoking" { + hash="$(shasum -a 256 "$FIXTURE/buildkite-gha")" + for hash in "${hash%% *}" "$(printf '%s' "${hash%% *}" | tr 'a-f' 'A-F')"; do + for MOCK_OS in Linux Darwin; do + [[ "$MOCK_OS" == Linux ]] && MOCK_ARCH=x86_64 || MOCK_ARCH=arm64 + export BUILDKITE_PLUGIN_CONFIGURATION="{\"binary\":\"https://example.com/custom#sha256=$hash\"}" + : > "$MOCK_LOG" + run "$REPO/hooks/command" + [ "$status" -eq 0 ] || { echo "$output"; false; } + [ "$(cat "$MOCK_LOG")" = https://example.com/custom ] + [ "$(cat "$FIXTURE/configuration")" = "$BUILDKITE_PLUGIN_CONFIGURATION" ] + assert_clean + done done } -@test "stops before setup when source resolution fails or is ambiguous" { - mock_source_resolution - export BUILDKITE_PLUGIN_GITHUB_ACTIONS_SOURCE_REF=missing - export MOCK_RESOLUTION_EXIT=2 - run "$REPO/hooks/command" - [ "$status" -ne 0 ] - [[ "$output" == *"could not resolve buildkite-gha source ref 'missing'"* ]] - [[ "$output" != *"Prepare workflows"* ]] - [ "$(grep -c '^mise=' "$MOCK_LOG")" -eq 0 ] - - export MOCK_RESOLUTION_EXIT=0 - export MOCK_SOURCE_REFS='abcdef0123456789abcdef0123456789abcdef01 refs/heads/missing -1234567890abcdef1234567890abcdef12345678 refs/tags/missing' +@test "never invokes an executable with a wrong checksum" { + export BUILDKITE_PLUGIN_CONFIGURATION='{"binary":{"linux/amd64":"https://example.com/custom#sha256=0000000000000000000000000000000000000000000000000000000000000000"}}' run "$REPO/hooks/command" [ "$status" -ne 0 ] - [[ "$output" == *"matches both a branch and tag"* ]] - [[ "$output" != *"Prepare workflows"* ]] - [ "$(grep -c '^mise=' "$MOCK_LOG")" -eq 0 ] - - for refs in 'not-a-commit refs/heads/missing' 'abcdef0123456789abcdef0123456789abcdef01 refs/heads/prefix/refs/heads/missing'; do - export MOCK_SOURCE_REFS="$refs" - run "$REPO/hooks/command" - [ "$status" -ne 0 ] - [[ "$output" == *"did not resolve to a full commit"* ]] - [[ "$output" != *"Prepare workflows"* ]] - [ "$(grep -c '^mise=' "$MOCK_LOG")" -eq 0 ] - done + [[ "$output" == *"binary checksum verification failed"* ]] + [ ! -e "$FIXTURE/arguments" ] + assert_clean } -@test "rejects invalid or ambiguous source configuration" { - mock_source_resolution - for ref in 'bad ref' 'main*' '../main' 'main^{commit}'; do - export BUILDKITE_PLUGIN_GITHUB_ACTIONS_SOURCE_REF="$ref" +@test "rejects malformed URLs, fragments, maps, conflicting and retired acquisition options before download" { + for configuration in \ + '{"version":"latest","binary":"https://example.com/custom"}' \ + '{"source-ref":"main"}' '{"minimum-release-age":"0s"}' \ + '{"binary":null}' '{"binary":{}}' '{"binary":[]}' \ + '{"binary":{"darwin/arm64":"https://example.com/mac"}}' \ + '{"binary":{"linux/amd64":"https://example.com/linux","windows/arm64":"https://example.com/windows"}}' \ + '{"binary":{"linux/amd64":"https://example.com/linux","windows/amd64":"http://example.com/windows"}}' \ + '{"binary":"http://example.com/custom"}' '{"binary":"/tmp/custom"}' \ + '{"binary":"https://user:password@example.com/custom"}' \ + '{"binary":"https://example.com/custom\u0000"}' \ + '{"binary":"https://example.com/custom#"}' \ + '{"binary":"https://example.com/custom#sha256=abc"}' \ + '{"binary":"https://example.com/custom#sha512=abc"}' \ + '{"binary":"https://example.com/custom#sha256=0000000000000000000000000000000000000000000000000000000000000000#sha256=0000000000000000000000000000000000000000000000000000000000000000"}'; do + export BUILDKITE_PLUGIN_CONFIGURATION="$configuration" run "$REPO/hooks/command" - [ "$status" -ne 0 ] - [[ "$output" == *"expected a full lowercase 40-character commit, branch, or tag"* ]] + [ "$status" -ne 0 ] || { echo "$configuration"; false; } [ ! -s "$MOCK_LOG" ] + [ ! -e "$FIXTURE/arguments" ] + assert_clean done +} - export BUILDKITE_PLUGIN_GITHUB_ACTIONS_VERSION=0.9.0 - for ref in abcdef0123456789abcdef0123456789abcdef01 main; do - export BUILDKITE_PLUGIN_GITHUB_ACTIONS_SOURCE_REF="$ref" +@test "rejects invalid configuration JSON, source refs and inexact versions" { + for configuration in '' 'not-json' '[]' 'null' '{} {}' \ + '{"version":null}' '{"version":1}' '{"version":""}' '{"version":"main"}' \ + '{"version":"v1.2.3\n"}' '{"version":"v1.2.3\u0000"}' \ + '{"version":"01.2.3"}' '{"version":"1.2"}' '{"version":"1.2.3-01"}' \ + '{"version":"1.2.3/../../bad"}'; do + export BUILDKITE_PLUGIN_CONFIGURATION="$configuration" run "$REPO/hooks/command" - [ "$status" -ne 0 ] - [[ "$output" == *"version and source-ref are mutually exclusive"* ]] + [ "$status" -ne 0 ] || { echo "$configuration"; false; } [ ! -s "$MOCK_LOG" ] done } -@test "rejects versions outside the stable plugin contract" { - for version in 0.8.0 0.9.0-rc.1 01.2.3 main ABCDEF0123456789ABCDEF0123456789ABCDEF01 abcdef0123456789abcdef0123456789abcdef0 '1.0.0/../../bad'; do - : > "$MOCK_LOG" - export BUILDKITE_PLUGIN_GITHUB_ACTIONS_VERSION="$version" +@test "rejects unsupported importer platforms including Windows" { + for host in Darwin:x86_64 Windows_NT:x86_64 Linux:riscv64; do + IFS=: read -r MOCK_OS MOCK_ARCH <<< "$host" run "$REPO/hooks/command" [ "$status" -ne 0 ] - [[ "$output" == *"expected latest or an exact stable release from 0.9.0 onward"* ]] + [[ "$output" == *"unsupported importer platform"* ]] [ ! -s "$MOCK_LOG" ] done } -@test "isolates mise settings while preserving an explicit data directory" { - export MISE_DATA_DIR="$TMP/trusted-mise-data" - export MISE_MINIMUM_RELEASE_AGE_EXCLUDES='github:*' - export MISE_PRERELEASES=1 - export MISE_URL_REPLACEMENTS='https://github.com|https://example.test' - export MISE_INSTALLS_DIR="$TMP/untrusted-installs" - export MISE_GITHUB_CREDENTIAL_COMMAND='echo credential-command-ran' - run "$REPO/hooks/command" - [ "$status" -eq 0 ] || { echo "$output"; false; } - grep -Fx 'prereleases=' "$MOCK_LOG" - grep -Fx 'url-replacements=' "$MOCK_LOG" - grep -Fx 'installs-dir=' "$MOCK_LOG" - grep -Fx 'credential-command=' "$MOCK_LOG" -} - -@test "leaves plugin parsing and Buildkite context normalization to buildkite-gha" { - export BUILDKITE_COMMIT=HEAD - export BUILDKITE_GROUP_LABEL="GitHub Actions / checks" - run "$REPO/hooks/command" - [ "$status" -eq 0 ] || { echo "$output"; false; } - grep -Fx "configuration=$BUILDKITE_PLUGIN_CONFIGURATION" "$MOCK_LOG" - grep -Fx 'commit=HEAD' "$MOCK_LOG" - grep -Fx 'group=GitHub Actions / checks' "$MOCK_LOG" - - unset BUILDKITE_PLUGIN_CONFIGURATION - run "$REPO/hooks/command" - [ "$status" -eq 2 ] - [[ "$output" == *"BUILDKITE_PLUGIN_CONFIGURATION is required"* ]] -} - -@test "passes future behavioral configuration through unchanged" { - export BUILDKITE_PLUGIN_CONFIGURATION='{"workflows":["ci.yml"],"future":{"nested":[true,3]},"runners":"validated-by-buildkite-gha"}' - run "$REPO/hooks/command" - [ "$status" -eq 0 ] || { echo "$output"; false; } - grep -Fx "configuration=$BUILDKITE_PLUGIN_CONFIGURATION" "$MOCK_LOG" -} - -@test "preserves buildkite-gha fail-closed configuration validation" { - export MOCK_IMPORTER_EXIT=2 - for configuration in \ - '{"workflow":"ci.yml","unknown":true}' \ - '{"workflow":"ci.yml","experimental-runner-user":"true"}'; do +@test "fails closed on missing or invalid latest tag metadata" { + for response in '{}' '{"tag_name":"main"}' '{"tag_name":"v1.2.3/../../bad"}' 'not-json'; do + printf '%s' "$response" > "$FIXTURE/release.json" : > "$MOCK_LOG" - export BUILDKITE_PLUGIN_CONFIGURATION="$configuration" run "$REPO/hooks/command" - [ "$status" -eq 2 ] || { echo "$output"; false; } - grep -Fx "configuration=$configuration" "$MOCK_LOG" + [ "$status" -ne 0 ] + [ "$(wc -l < "$MOCK_LOG")" -eq 1 ] + [ ! -e "$FIXTURE/arguments" ] + assert_clean done } -@test "ignores legacy aliases and propagates buildkite-gha failures" { - export BUILDKITE_PLUGIN__VERSION=0.9.0 - export BUILDKITE_PLUGIN__MINIMUM_RELEASE_AGE=7d - export MOCK_IMPORTER_EXIT=37 - run "$REPO/hooks/command" - [ "$status" -eq 37 ] || { echo "$output"; false; } - grep -Fx 'mise=--no-config exec github:buildkite/buildkite-gha@latest -- buildkite-gha plugin' "$MOCK_LOG" - grep -Fx 'minimum-release-age=0s' "$MOCK_LOG" -} - -@test "rejects unsupported platforms before invoking mise" { - for platform in linux/arm64 darwin/amd64; do - : > "$MOCK_LOG" - if [[ "$platform" == linux/arm64 ]]; then - cat > "$TMP/bin/uname" <<'EOF' -#!/usr/bin/env bash -[[ "$1" == -s ]] && echo Linux || echo arm64 -EOF - else - cat > "$TMP/bin/uname" <<'EOF' -#!/usr/bin/env bash -[[ "$1" == -s ]] && echo Darwin || echo x86_64 -EOF - fi - chmod +x "$TMP/bin/uname" +@test "preserves curl failure and cleans partial downloads at every acquisition stage" { + for MOCK_FAIL_SUFFIX in /latest .tar.gz checksums.txt /custom; do + export MOCK_FAIL_SUFFIX + [[ "$MOCK_FAIL_SUFFIX" == /custom ]] && export BUILDKITE_PLUGIN_CONFIGURATION='{"binary":"https://example.com/custom"}' run "$REPO/hooks/command" - [ "$status" -ne 0 ] - [[ "$output" == *"only Linux amd64 and Darwin arm64 are supported"* ]] - [ ! -s "$MOCK_LOG" ] + [ "$status" -eq 22 ] + [[ "$output" == *"curl: fixture HTTP failure"* ]] + [ ! -e "$FIXTURE/arguments" ] + assert_clean done } -@test "installs a pinned verified mise when it is not on PATH and reuses it" { - prepare_bootstrap - run "$REPO/hooks/command" - [ "$status" -eq 0 ] || { echo "$output"; false; } - [[ "$output" == *"installing mise 2026.10.17"* ]] - grep -F 'curl=--disable --fail --silent --show-error --location --proto =https --proto-redir =https --tlsv1.2 https://github.com/jdx/mise/releases/download/v2026.10.17/mise-v2026.10.17-linux-x64-musl.tar.gz --output ' "$MOCK_LOG" - grep -F 'sha256sum=' "$MOCK_LOG" - [ -x "$MISE_DATA_DIR/github-actions-buildkite-plugin/mise/2026.10.17/linux-amd64/mise" ] - grep -Fx 'mise=--no-config exec github:buildkite/buildkite-gha@latest -- buildkite-gha plugin' "$MOCK_LOG" - - : > "$MOCK_LOG" - run "$REPO/hooks/command" - [ "$status" -eq 0 ] || { echo "$output"; false; } - ! grep -q '^curl=' "$MOCK_LOG" - grep -Fx 'mise=--no-config exec github:buildkite/buildkite-gha@latest -- buildkite-gha plugin' "$MOCK_LOG" -} - -@test "installs the pinned macOS arm64 mise asset with its platform checksum" { - prepare_bootstrap - mock_host darwin/arm64 - export MOCK_MISE_SHA256=d7c50c1683524a764260986ccc9c41c0de5e1fcd491714b5640b657b76781af6 - run "$REPO/hooks/command" - [ "$status" -eq 0 ] || { echo "$output"; false; } - grep -F 'https://github.com/jdx/mise/releases/download/v2026.10.17/mise-v2026.10.17-macos-arm64.tar.gz --output ' "$MOCK_LOG" - grep -E '^shasum=-a 256 ' "$MOCK_LOG" - ! grep -q '^sha256sum=' "$MOCK_LOG" - [ -x "$MISE_DATA_DIR/github-actions-buildkite-plugin/mise/2026.10.17/darwin-arm64/mise" ] -} - -@test "replaces an old mise and stops when bootstrap verification fails" { - prepare_bootstrap - write_mise "$TMP/bin/mise" 2025.1.0 - export MOCK_MISE_SHA256=0000000000000000000000000000000000000000000000000000000000000000 - run "$REPO/hooks/command" - [ "$status" -ne 0 ] - [[ "$output" == *"mise on PATH is older than 2026.10.17"* ]] - [[ "$output" == *"mise release archive checksum verification failed"* ]] - ! grep -q '^mise=--no-config exec ' "$MOCK_LOG" -} - -@test "accepts the qualified mise minimum and later versions only" { - for version in 2026.10.17 2026.10.18 2026.11.0 2027.1.0; do - write_mise "$TMP/bin/mise" "$version" - run bash -c 'source "$1/lib/mise.sh"; mise_is_compatible "$2"' _ "$REPO" "$TMP/bin/mise" - [ "$status" -eq 0 ] || { echo "$version: $output"; false; } - done - for version in 2025.12.99 2026.5.12 2026.8.4 2026.9.99 2026.10.15 2026.10.16; do - write_mise "$TMP/bin/mise" "$version" - run bash -c 'source "$1/lib/mise.sh"; mise_is_compatible "$2"' _ "$REPO" "$TMP/bin/mise" +@test "rejects missing, duplicate and incorrect release checksums without invoking" { + for failure in missing duplicate mismatch; do + checksums + case "$failure" in + missing) : > "$FIXTURE/checksums.txt" ;; + duplicate) head -n 1 "$FIXTURE/checksums.txt" >> "$FIXTURE/checksums.txt" ;; + mismatch) printf 'tampering' >> "$FIXTURE/archive.tar.gz" ;; + esac + run "$REPO/hooks/command" [ "$status" -ne 0 ] + [[ "$output" == *checksum* ]] + [ ! -e "$FIXTURE/arguments" ] + assert_clean done } -@test "replaces unremediated PATH and managed mise copies before execution" { - prepare_bootstrap - cached="$MISE_DATA_DIR/github-actions-buildkite-plugin/mise/2026.10.17/linux-amd64/mise" - mkdir -p "$(dirname "$cached")" - for version in 2026.5.12 2026.8.4 2026.10.15 2026.10.16; do - : > "$MOCK_LOG" - write_mise "$TMP/bin/mise" "$version" - write_mise "$cached" "$version" +@test "rejects archives with missing, linked, or duplicate executable members" { + for failure in missing symlink hardlink duplicate; do + case "$failure" in + missing) tar -czf "$FIXTURE/archive.tar.gz" -C "$FIXTURE" LICENSE ;; + symlink) + rm "$FIXTURE/buildkite-gha" + ln -s LICENSE "$FIXTURE/buildkite-gha" + tar -czf "$FIXTURE/archive.tar.gz" -C "$FIXTURE" buildkite-gha ;; + hardlink) + rm "$FIXTURE/buildkite-gha" + ln "$FIXTURE/LICENSE" "$FIXTURE/buildkite-gha" + tar -czf "$FIXTURE/archive.tar.gz" -C "$FIXTURE" LICENSE buildkite-gha ;; + duplicate) tar -czf "$FIXTURE/archive.tar.gz" -C "$FIXTURE" buildkite-gha buildkite-gha ;; + esac + checksums run "$REPO/hooks/command" - [ "$status" -eq 0 ] || { echo "$output"; false; } - [[ "$output" == *"mise on PATH is older than 2026.10.17"* ]] - [[ "$output" == *"installing mise 2026.10.17"* ]] - grep -q '^curl=' "$MOCK_LOG" - run "$cached" version - [[ "$output" == "2026.10.17 linux-x64" ]] + [ "$status" -ne 0 ] || { echo "$failure: $output"; false; } + [ ! -e "$FIXTURE/arguments" ] + assert_clean done } From e941df0f18f35f8fea8e7f39cb82333d91220f4f Mon Sep 17 00:00:00 2001 From: Samuel Cochran Date: Mon, 2 Nov 2026 12:06:17 +0000 Subject: [PATCH 2/4] Keep native importer smoke workflow keys distinct Restore separate fixture paths because generated step keys are derived from workflow paths. Amp-Thread-ID: https://ampcode.com/threads/T-e386451c-f404-41b8-8d80-f10d9ee76197 Co-authored-by: Amp --- .../plugin-native-importer-release-macos.yml | 16 +++++++++++ .../plugin-native-importer-release-mixed.yml | 28 +++++++++++++++++++ .buildkite/pipeline.yml | 5 ++-- 3 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 .buildkite/fixtures/plugin-native-importer-release-macos.yml create mode 100644 .buildkite/fixtures/plugin-native-importer-release-mixed.yml diff --git a/.buildkite/fixtures/plugin-native-importer-release-macos.yml b/.buildkite/fixtures/plugin-native-importer-release-macos.yml new file mode 100644 index 0000000..33cd20c --- /dev/null +++ b/.buildkite/fixtures/plugin-native-importer-release-macos.yml @@ -0,0 +1,16 @@ +name: Plugin native importer macOS-only release smoke + +on: + push: + +jobs: + native-importer-macos-only: + name: Verify released native importer macOS-only graph + runs-on: macos-14 + steps: + - name: Verify macOS runtime + shell: bash + run: | + test "${GITHUB_ACTIONS:-}" = true + test "${RUNNER_OS:-}" = macOS + test "$(uname -m)" = arm64 diff --git a/.buildkite/fixtures/plugin-native-importer-release-mixed.yml b/.buildkite/fixtures/plugin-native-importer-release-mixed.yml new file mode 100644 index 0000000..2b791a3 --- /dev/null +++ b/.buildkite/fixtures/plugin-native-importer-release-mixed.yml @@ -0,0 +1,28 @@ +name: Plugin native importer mixed release smoke + +on: + push: + +jobs: + native-importer-mixed-linux: + name: Verify released native importer mixed Linux job + runs-on: ubuntu-latest + steps: + - name: Verify Linux runtime + shell: bash + run: | + test "${GITHUB_ACTIONS:-}" = true + test "${RUNNER_OS:-}" = Linux + test -d /opt/hostedtoolcache + + native-importer-mixed-macos: + name: Verify released native importer Linux to macOS dependency + needs: native-importer-mixed-linux + runs-on: macos-14 + steps: + - name: Verify macOS runtime + shell: bash + run: | + test "${GITHUB_ACTIONS:-}" = true + test "${RUNNER_OS:-}" = macOS + test "$(uname -m)" = arm64 diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 4c14239..a217ef2 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -98,7 +98,8 @@ steps: plugins: - github-actions#$$commit: workflows: - - .buildkite/fixtures/plugin-release-macos.yml + # Distinct paths prevent step-key collisions with Linux importers. + - .buildkite/fixtures/plugin-native-importer-release-macos.yml version: 0.107.1 runners: - runs-on: macos-14 @@ -113,7 +114,7 @@ steps: plugins: - github-actions#$$commit: workflows: - - .buildkite/fixtures/plugin-release-mixed.yml + - .buildkite/fixtures/plugin-native-importer-release-mixed.yml version: 0.107.1 experimental-runner-user: false runners: From 5499146a46bb51380a5ec60859dde2c650d42845 Mon Sep 17 00:00:00 2001 From: Samuel Cochran Date: Mon, 9 Nov 2026 05:12:11 +0000 Subject: [PATCH 3/4] Focus plugin README on end-user setup Recommend the default CLI release and keep optional pinning guidance. Move custom binary examples and acquisition details into DEVELOPMENT.md. Amp-Thread-ID: https://ampcode.com/threads/T-e386451c-f404-41b8-8d80-f10d9ee76197 Co-authored-by: Amp Amp-Thread-ID: https://ampcode.com/threads/T-e386451c-f404-41b8-8d80-f10d9ee76197 --- DEVELOPMENT.md | 42 ++++++++++++++++++++++++++++++++++++++ README.md | 55 ++++++++------------------------------------------ 2 files changed, 50 insertions(+), 47 deletions(-) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 8cba249..e7f9464 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -1,5 +1,47 @@ # Development +## Use a custom executable + +Build the CLI outside the plugin, then publish it at an HTTPS URL. Use `binary` to test that build instead of an official release. Don't combine `binary` with an explicit `version`, including `version: latest`. + +> [!IMPORTANT] +> Use a CLI build that supports `binary` configuration. Older CLIs reject this property. + +Use a raw executable URL, not an archive, local path, or URL template. Prefer an immutable URL with a `#sha256=` fragment containing its 64-digit hexadecimal SHA256. The fragment is removed before download, and the executable bytes are verified before execution. Plain HTTPS URLs remain available for development; other fragments are rejected. + +```yaml +plugins: + - github-actions#latest: + workflow: .github/workflows/ci.yml + binary: "https://example.com/buildkite-gha-linux-amd64#sha256=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +``` + +Replace the example URL and checksum with your executable's values. A single URL covers the importer platform only. For workflows that need other platforms, supply a map with the importer and each required counterpart: + +```yaml +plugins: + - github-actions#latest: + workflow: .github/workflows/ci.yml + binary: + linux/amd64: https://example.com/buildkite-gha-linux-amd64 + linux/arm64: https://example.com/buildkite-gha-linux-arm64 + darwin/arm64: https://example.com/buildkite-gha-darwin-arm64 + windows/amd64: https://example.com/buildkite-gha-windows-amd64.exe +``` + +Each map URL also accepts a checksum fragment. The plugin downloads only its importer executable and forwards the configuration unchanged. The companion CLI downloads and verifies counterparts only when needed, with no release fallback for a missing platform. Use matching builds across the map. Windows amd64 is a counterpart platform only. + +> [!WARNING] +> Custom URLs select code that runs with the importer's authority. HTTPS protects transport, not publisher trust; a checksum pins bytes but does not establish who built them. Use trusted publishers and do not put credentials in URLs. URLs remain in pipeline configuration and may appear in errors. + +`source-ref` and `minimum-release-age` are retired and fail with an error. Use `binary` to test unreleased code and an exact `version` instead of delaying `latest`. + +## Binary acquisition + +`latest` resolves once through GitHub's releases API. The plugin downloads the selected platform's official archive and `checksums.txt` from that exact release, verifies SHA256, and extracts only the executable. It uses a private temporary directory, removes it on exit, and keeps no installer cache. It does not install mise or build Go source. + +The plugin validates acquisition properties only. All other options, including `workflow`, `workflows`, `runners`, and `oidc`, pass unchanged to the selected CLI for strict validation. + ## Run local checks Install [mise](https://mise.jdx.dev/), then run: diff --git a/README.md b/README.md index 3ceb1bc..7791746 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # GitHub Actions Buildkite plugin -Run supported GitHub Actions workflows as native Buildkite Pipelines jobs. The plugin downloads and launches [`buildkite-gha`](https://github.com/buildkite/buildkite-gha); the CLI selects workflows, validates behavioral configuration, and uploads the generated pipeline. +Run supported GitHub Actions workflows as native Buildkite Pipelines jobs using [`buildkite-gha`](https://github.com/buildkite/buildkite-gha). > [!NOTE] > This integration is in public preview. See [Run GitHub Actions workflows](https://buildkite.com/docs/pipelines/migration/run-github-actions-workflows) for setup, compatibility, configuration, and security guidance. [Report issues](https://github.com/buildkite/buildkite-gha/issues) in the CLI repository. @@ -18,22 +18,15 @@ steps: workflow: .github/workflows/ci.yml ``` +Leave `version` unset unless you need to pin a CLI release. By default, the plugin uses the latest stable CLI release. + The importer's `agents` configuration selects its queue. Configure a Linux amd64/arm64 or native macOS arm64 agent; Windows importers are not supported. `runners` mappings select generated jobs, not the importer. The importer needs Bash, `curl`, `jq`, `tar`, `mktemp`, and `sha256sum` (Linux) or `shasum` (macOS), plus outbound HTTPS access to GitHub. See the product documentation for Buildkite Agent and generated-job requirements. -The ref after `github-actions#` selects the plugin: use `github-actions#v0.14.0` for an immutable pin or `github-actions#latest` for the newest promoted stable plugin. The separate `version` property selects the CLI. - -## Select the executable - -| Property | Default | Description | -| --- | --- | --- | -| `version` | `latest` | Latest stable CLI release or an exact release tag, with or without `v`, including prereleases. | -| `binary` | — | HTTPS URL of a raw importer executable, or a map of platform names to executable URLs. Mutually exclusive with an explicit `version`. Requires companion CLI support; see below. | +## Pin a release when needed -`latest` resolves once through GitHub's releases API. The plugin downloads the selected platform's official archive and `checksums.txt` from that exact release, verifies SHA256, and extracts only the executable. It uses a private temporary directory, removes it on exit, and keeps no installer cache. It does not install mise or build Go source. - -To pin a CLI release: +The ref after `github-actions#` selects the plugin: `github-actions#latest` follows the newest promoted stable plugin; an exact tag such as `github-actions#v0.14.0` pins the plugin. The separate `version` property pins the CLI: ```yaml plugins: @@ -42,47 +35,15 @@ plugins: version: "0.107.1" ``` -`source-ref` and `minimum-release-age` are retired and fail with an error. Publish a built executable and use `binary` to test unreleased code; use an exact `version` instead of delaying `latest`. - -### Use a custom executable - -> [!IMPORTANT] -> `binary` requires the companion `buildkite-gha` CLI support and is not supported by existing released CLIs. Use a build containing that support; older CLIs reject this property. - -Use a raw executable URL, not an archive, local path, or URL template. Prefer an immutable URL with a `#sha256=` fragment containing its 64-digit hexadecimal SHA256. The fragment is removed before download, and the executable bytes are verified before execution. Plain HTTPS URLs remain available for development; other fragments are rejected. - -```yaml -plugins: - - github-actions#latest: - workflow: .github/workflows/ci.yml - binary: "https://example.com/buildkite-gha-linux-amd64#sha256=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" -``` - -Replace the example URL and checksum with your executable's values. A single URL covers the importer platform only. For workflows that need other platforms, supply a map with the importer and each required counterpart: - -```yaml -plugins: - - github-actions#latest: - workflow: .github/workflows/ci.yml - binary: - linux/amd64: https://example.com/buildkite-gha-linux-amd64 - linux/arm64: https://example.com/buildkite-gha-linux-arm64 - darwin/arm64: https://example.com/buildkite-gha-darwin-arm64 - windows/amd64: https://example.com/buildkite-gha-windows-amd64.exe -``` - -Each map URL also accepts a checksum fragment. The plugin downloads only its importer executable and forwards the configuration unchanged. The companion CLI downloads and verifies counterparts only when needed, with no release fallback for a missing platform. Use matching builds across the map. Windows amd64 is a counterpart platform only. - -> [!WARNING] -> Custom URLs select code that runs with the importer's authority. HTTPS protects transport, not publisher trust; a checksum pins bytes but does not establish who built them. Use trusted publishers and do not put credentials in URLs. URLs remain in pipeline configuration and may appear in errors. +`version` accepts `latest` or an exact release tag, with or without `v`, including prereleases. Remove `version` to resume following stable CLI releases. ## Configure workflow behavior -The plugin validates acquisition properties only. All other options, including `workflow`, `workflows`, `runners`, and `oidc`, pass unchanged to the selected CLI for strict validation. See the [product documentation](https://buildkite.com/docs/pipelines/migration/run-github-actions-workflows) for supported options and examples rather than assuming a newer option works with an older CLI. +See the [product documentation](https://buildkite.com/docs/pipelines/migration/run-github-actions-workflows) for workflow selection, runner mappings, OIDC, and supported options. If you pin an older CLI, check that it supports the options you use. > [!WARNING] > Generated jobs can run untrusted workflow and action code. Use whole-job isolation, clean machines, and no ambient protected credentials. Review the product's security guidance before enabling private repository access, secrets, or temporary GitHub tokens. ## Development and license -See [Development](DEVELOPMENT.md) for local checks, CI coverage, and release safety. This project uses the [MIT License](LICENSE). +For testing unreleased builds, see [Use a custom executable](DEVELOPMENT.md#use-a-custom-executable). [Development](DEVELOPMENT.md) also covers local checks, CI coverage, and release safety. This project uses the [MIT License](LICENSE). From 93b1cc82416a456b57a43d0c7a806d05184e25e1 Mon Sep 17 00:00:00 2001 From: Samuel Cochran Date: Mon, 9 Nov 2026 05:36:46 +0000 Subject: [PATCH 4/4] Show native GitHub Actions pipeline setup Amp-Thread-ID: https://ampcode.com/threads/T-e386451c-f404-41b8-8d80-f10d9ee76197 Co-authored-by: Amp --- DEVELOPMENT.md | 2 ++ README.md | 38 ++++++++++++++++---------------------- 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index e7f9464..09dcbc0 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -38,6 +38,8 @@ Each map URL also accepts a checksum fragment. The plugin downloads only its imp ## Binary acquisition +For custom importers, use a Linux amd64/arm64 or native macOS arm64 agent with the dependencies in [`plugin.yml`](plugin.yml) and outbound HTTPS access to GitHub. Windows importers are not supported. The importer's `agents` configuration selects its queue; `runners` mappings select generated jobs, not the importer. + `latest` resolves once through GitHub's releases API. The plugin downloads the selected platform's official archive and `checksums.txt` from that exact release, verifies SHA256, and extracts only the executable. It uses a private temporary directory, removes it on exit, and keeps no installer cache. It does not install mise or build Go source. The plugin validates acquisition properties only. All other options, including `workflow`, `workflows`, `runners`, and `oidc`, pass unchanged to the selected CLI for strict validation. diff --git a/README.md b/README.md index 7791746..58c91a6 100644 --- a/README.md +++ b/README.md @@ -5,41 +5,35 @@ Run supported GitHub Actions workflows as native Buildkite Pipelines jobs using > [!NOTE] > This integration is in public preview. See [Run GitHub Actions workflows](https://buildkite.com/docs/pipelines/migration/run-github-actions-workflows) for setup, compatibility, configuration, and security guidance. [Report issues](https://github.com/buildkite/buildkite-gha/issues) in the CLI repository. -## Add the plugin +## Usage + +[Create a Buildkite pipeline connected to your GitHub repository](https://buildkite.com/docs/pipelines/migration/run-github-actions-workflows#add-a-github-actions-workflow-to-a-pipeline-create-a-github-actions-pipeline) and your builds will automatically use this plugin to run supported workflows unchanged on Buildkite. + +Your new pipeline will have steps like: ```yaml steps: - label: ":github: GitHub Actions" - key: github-actions - agents: - queue: importer-linux - plugins: - - github-actions#latest: - workflow: .github/workflows/ci.yml + plugin: github-actions ``` -Leave `version` unset unless you need to pin a CLI release. By default, the plugin uses the latest stable CLI release. - -The importer's `agents` configuration selects its queue. Configure a Linux amd64/arm64 or native macOS arm64 agent; Windows importers are not supported. `runners` mappings select generated jobs, not the importer. - -The importer needs Bash, `curl`, `jq`, `tar`, `mktemp`, and `sha256sum` (Linux) or `shasum` (macOS), plus outbound HTTPS access to GitHub. See the product documentation for Buildkite Agent and generated-job requirements. +> You can add optional configuration if needed. -## Pin a release when needed +## Configuration -The ref after `github-actions#` selects the plugin: `github-actions#latest` follows the newest promoted stable plugin; an exact tag such as `github-actions#v0.14.0` pins the plugin. The separate `version` property pins the CLI: +You can pin the plugin with an exact tag and the `buildkite-gha` runtime with `version`: ```yaml -plugins: - - github-actions#latest: - workflow: .github/workflows/ci.yml - version: "0.107.1" +steps: + - label: ":github: GitHub Actions" + plugin: + github-actions#v0.14.0: + version: v0.124.0 ``` -`version` accepts `latest` or an exact release tag, with or without `v`, including prereleases. Remove `version` to resume following stable CLI releases. - -## Configure workflow behavior +We recommend omitting both pins to follow the latest stable versions, especially during the preview. `version` also accepts `latest` or an exact release tag without the `v` prefix, including prereleases. -See the [product documentation](https://buildkite.com/docs/pipelines/migration/run-github-actions-workflows) for workflow selection, runner mappings, OIDC, and supported options. If you pin an older CLI, check that it supports the options you use. +Additional plugin configuration is passed to [the `buildkite-gha` runtime](https://github.com/buildkite/buildkite-gha). See [the documentation](https://buildkite.com/docs/pipelines/migration/run-github-actions-workflows) for supported options. If you pin an older runtime, check that it supports the options you use. > [!WARNING] > Generated jobs can run untrusted workflow and action code. Use whole-job isolation, clean machines, and no ambient protected credentials. Review the product's security guidance before enabling private repository access, secrets, or temporary GitHub tokens.