Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,10 @@ The Buildkite pipeline's required released-default smoke test:
3. Uploads `.github/workflows/plugin-smoke.yml`
4. Checks out the same commit in a generated hosted job and verifies the release defaults

This lane requires Linux x86-64 Hosted Agents, but no configured secrets, provider tokens, or cache service. Cache integration is tested separately in the `buildkite/buildkite-gha` demo pipeline.
This lane requires Linux x86-64 Hosted Agents, but no configured secrets or cache service. Cache integration is tested separately in the `buildkite/buildkite-gha` demo pipeline.

Set `GITHUB_ACTIONS_SOURCE_REF` to `latest` or a full lowercase 40-character CLI commit on a manually created Buildkite build to run the optional source smoke instead. This lane uses the repository's `mise.toml` to install pinned mise 2026.5.12 and Go 1.26.5, pins the plugin to the build's exact commit, and exercises `buildkite-gha-source-ref` end to end. Ordinary builds continue to prove the released archive and installation path.

The smoke tests leave `private-checkout` unset so they remain service-free. Bats covers its argument wiring; the `buildkite/buildkite-gha` repository tests its runtime behavior with GHAC minting.

## Release

Plugin releases are Git tags and contain no generated assets. Before creating a `v0.x.y` tag, verify that:
Expand Down
21 changes: 2 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@ The importer step must have a `key`. Each workflow job and static matrix entry b
| `workflow` | Yes | — | Path to the GitHub Actions workflow. |
| `version` | No | `0.6.0` | Exact pre-1.0 `buildkite-gha` CLI version. |
| `buildkite-gha-source-ref` | No | — | `latest` or a full lowercase commit for unreleased CLI testing. |
| `private-checkout` | No | `false` | Enable read-only checkout of the pipeline's private GitHub repository. |

The plugin release (`github-actions#v0.6.0`) and CLI `version` are independent. Set `version` only when you need a CLI release other than the default. `version` and `buildkite-gha-source-ref` are mutually exclusive.

Repository checkout behavior is owned by `buildkite-gha` and Buildkite's repository-provider backend. Workflow permissions remain separate: checkout credentials do not populate `GITHUB_TOKEN` or `github.token`, enable private actions, or permit alternate repositories or refs.

## Compatibility

`buildkite-gha` intentionally supports a subset of GitHub Actions. For the default CLI, see the [`v0.6.0` compatibility guide](https://github.com/buildkite/buildkite-gha/blob/v0.6.0/docs/compatibility.md) before migrating a workflow. Unsupported behavior fails explicitly rather than silently choosing a substitute.
Expand All @@ -41,24 +42,6 @@ Configure branch, tag, schedule, and pull request triggers in Buildkite. The wor

Released mode downloads the selected public `buildkite/buildkite-gha` release without a GitHub token and does not require importer-side mise. Downloads and cached copies are verified before execution. Generated jobs prepare mise only when their resolved action trees can execute JavaScript; shell-only, native-adapter-only, and Docker-only jobs skip that setup.

## Private repositories

By default, generated `actions/checkout` steps perform a credential-free, shallow checkout, so the workflow repository must be public. Set `private-checkout` to give verified checkout jobs read-only access to the pipeline's exact GitHub repository:

```yaml
steps:
- label: ":github: GitHub Actions"
key: "github-actions"
plugins:
- github-actions#v0.6.0:
workflow: .github/workflows/ci.yml
private-checkout: true
```

This requires Buildkite's job-bound GitHub scoped access-token service. The CLI requests fixed `contents:read` authority, and the service independently requires the event repository to match the pipeline's GitHub repository. The credential is redacted before use and supplied only to Git through a one-shot askpass pipe.

This option does not populate `GITHUB_TOKEN` or `github.token`, grant write access, enable private actions, or permit alternate repositories or refs.

## Caching

On hosted agents, attach the plugin's cache volume to speed up the importer:
Expand Down
13 changes: 1 addition & 12 deletions hooks/command
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ PLUGIN_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
workflow="${BUILDKITE_PLUGIN_GITHUB_ACTIONS_WORKFLOW:-${BUILDKITE_PLUGIN__WORKFLOW:-}}"
configured_version="${BUILDKITE_PLUGIN_GITHUB_ACTIONS_VERSION:-${BUILDKITE_PLUGIN__VERSION:-}}"
source_ref="${BUILDKITE_PLUGIN_GITHUB_ACTIONS_BUILDKITE_GHA_SOURCE_REF:-${BUILDKITE_PLUGIN__BUILDKITE_GHA_SOURCE_REF:-}}"
private_checkout="${BUILDKITE_PLUGIN_GITHUB_ACTIONS_PRIVATE_CHECKOUT:-${BUILDKITE_PLUGIN__PRIVATE_CHECKOUT:-false}}"

if [[ -z "$workflow" ]]; then
echo "github-actions plugin: workflow is required" >&2
Expand All @@ -20,12 +19,6 @@ if [[ "$workflow" == -* || "$workflow" == *$'\n'* || "$workflow" == *$'\r'* ]];
echo "github-actions plugin: workflow must be a path, not an option or multiline value" >&2
exit 1
fi
# An unrecognised value must not silently select either behaviour: treating it as
# false hides the opt-in, and treating it as true would widen authority.
case "$private_checkout" in
true|false) ;;
*) echo "github-actions plugin: private-checkout must be true or false" >&2; exit 1 ;;
esac
if [[ -n "$source_ref" && -n "$configured_version" ]]; then
echo "github-actions plugin: version and buildkite-gha-source-ref are mutually exclusive" >&2
exit 1
Expand Down Expand Up @@ -55,11 +48,7 @@ fi

echo "~~~ :github: Prepare workflow"

upload_args=(upload)
if [[ "$private_checkout" == true ]]; then
upload_args+=(--private-checkout)
fi
upload_args+=(--runtime-queue hosted "$workflow")
upload_args=(upload "$workflow")

if [[ -n "$source_ref" ]]; then
resolved_source_ref="$source_ref"
Expand Down
4 changes: 0 additions & 4 deletions plugin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,6 @@ configuration:
type: string
pattern: "^(latest|[0-9a-f]{40})$"
description: Build buildkite-gha from the latest main commit or an exact full commit using config-free mise and pinned Go. Mutually exclusive with version.
private-checkout:
type: boolean
default: false
description: Opt verified checkout jobs into read-only authority for the pipeline's exact GitHub repository.
required:
- workflow
additionalProperties: false
68 changes: 26 additions & 42 deletions tests/command.bats
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ setup() {
export BUILDKITE_COMMIT=1111111111111111111111111111111111111111
unset BUILDKITE_PLUGIN_GITHUB_ACTIONS_VERSION BUILDKITE_PLUGIN__VERSION
unset BUILDKITE_PLUGIN_GITHUB_ACTIONS_BUILDKITE_GHA_SOURCE_REF BUILDKITE_PLUGIN__BUILDKITE_GHA_SOURCE_REF
unset BUILDKITE_USE_REPOSITORY_PROVIDER_GIT_CREDENTIALS BUILDKITE_USE_GITHUB_APP_GIT_CREDENTIALS
export MOCK_LOG="$TMP/mock.log"
mkdir -p "$TMP/bin" "$TMP/payload"
: > "$MOCK_LOG"
Expand Down Expand Up @@ -94,7 +95,9 @@ teardown() { rm -rf "$TMP"; }
mkdir -p "$TMPDIR"
run "$REPO/hooks/command"
[ "$status" -eq 0 ] || { echo "$output"; false; }
grep -Fx 'upload --runtime-queue hosted .github/workflows/ci.yml' "$MOCK_LOG"
grep -Fx 'upload .github/workflows/ci.yml' "$MOCK_LOG"
! grep -q -- '--runtime-queue' "$MOCK_LOG"
! grep -q -- '--private-checkout' "$MOCK_LOG"
grep -Fx 'https://github.com/buildkite/buildkite-gha/releases/download/v0.6.0/buildkite-gha_Linux_x86_64.tar.gz' "$MOCK_LOG"
grep -Fx 'https://github.com/buildkite/buildkite-gha/releases/download/v0.6.0/checksums.txt' "$MOCK_LOG"
grep -Fx "path=$EXPECTED_PATH" "$MOCK_LOG"
Expand Down Expand Up @@ -131,41 +134,18 @@ teardown() { rm -rf "$TMP"; }
! grep -q '^upload ' "$MOCK_LOG"
}

@test "opts into private checkout only when explicitly enabled" {
export BUILDKITE_PLUGIN_GITHUB_ACTIONS_PRIVATE_CHECKOUT=true
run "$REPO/hooks/command"
[ "$status" -eq 0 ] || { echo "$output"; false; }
grep -Fx 'upload --private-checkout --runtime-queue hosted .github/workflows/ci.yml' "$MOCK_LOG"

: > "$MOCK_LOG"
export BUILDKITE_PLUGIN_GITHUB_ACTIONS_PRIVATE_CHECKOUT=false
run "$REPO/hooks/command"
[ "$status" -eq 0 ] || { echo "$output"; false; }
grep -Fx 'upload --runtime-queue hosted .github/workflows/ci.yml' "$MOCK_LOG"

: > "$MOCK_LOG"
unset BUILDKITE_PLUGIN_GITHUB_ACTIONS_PRIVATE_CHECKOUT
run "$REPO/hooks/command"
[ "$status" -eq 0 ] || { echo "$output"; false; }
grep -Fx 'upload --runtime-queue hosted .github/workflows/ci.yml' "$MOCK_LOG"

# An empty value falls back to the unprivileged default, as `version` does.
: > "$MOCK_LOG"
export BUILDKITE_PLUGIN_GITHUB_ACTIONS_PRIVATE_CHECKOUT=
run "$REPO/hooks/command"
[ "$status" -eq 0 ] || { echo "$output"; false; }
grep -Fx 'upload --runtime-queue hosted .github/workflows/ci.yml' "$MOCK_LOG"
}

@test "rejects a non-boolean private-checkout without running importer" {
for value in yes 1 TRUE ' true' '--event-path'; do
: > "$MOCK_LOG"
export BUILDKITE_PLUGIN_GITHUB_ACTIONS_PRIVATE_CHECKOUT="$value"
run "$REPO/hooks/command"
[ "$status" -ne 0 ] || { echo "accepted '$value'"; false; }
[[ "$output" == *"private-checkout must be true or false"* ]]
run grep -q '^upload ' "$MOCK_LOG"
[ "$status" -eq 1 ]
@test "repository-provider credential signals do not alter the upload command" {
for signal in BUILDKITE_USE_REPOSITORY_PROVIDER_GIT_CREDENTIALS BUILDKITE_USE_GITHUB_APP_GIT_CREDENTIALS; do
for value in true false; do
: > "$MOCK_LOG"
export "$signal"="$value"
run "$REPO/hooks/command"
[ "$status" -eq 0 ] || { echo "$output"; false; }
grep -Fx 'upload .github/workflows/ci.yml' "$MOCK_LOG"
! grep -q -- '--runtime-queue' "$MOCK_LOG"
! grep -q -- '--private-checkout' "$MOCK_LOG"
done
unset "$signal"
done
}

Expand Down Expand Up @@ -194,7 +174,9 @@ teardown() { rm -rf "$TMP"; }
[ "$status" -eq 0 ] || { echo "$output"; false; }
[[ "$output" == *"buildkite-gha source latest resolved to $MOCK_SOURCE_SHA"* ]]
grep -Fx 'git:ls-remote --exit-code --refs https://github.com/buildkite/buildkite-gha.git refs/heads/main' "$MOCK_LOG"
grep -Fx "mise:--no-config x go@1.26.5 -- env CGO_ENABLED=0 GOTOOLCHAIN=local go run -trimpath github.com/buildkite/buildkite-gha/cmd/buildkite-gha@$MOCK_SOURCE_SHA upload --runtime-queue hosted .github/workflows/ci.yml" "$MOCK_LOG"
grep -Fx "mise:--no-config x go@1.26.5 -- env CGO_ENABLED=0 GOTOOLCHAIN=local go run -trimpath github.com/buildkite/buildkite-gha/cmd/buildkite-gha@$MOCK_SOURCE_SHA upload .github/workflows/ci.yml" "$MOCK_LOG"
! grep -q -- '--runtime-queue' "$MOCK_LOG"
! grep -q -- '--private-checkout' "$MOCK_LOG"
grep -Fx 'source-env:MISE_YES=1' "$MOCK_LOG"
run grep -F 'releases/download' "$MOCK_LOG"
[ "$status" -eq 1 ]
Expand All @@ -206,7 +188,9 @@ teardown() { rm -rf "$TMP"; }
run "$REPO/hooks/command"
[ "$status" -eq 0 ] || { echo "$output"; false; }
! grep -q '^git:' "$MOCK_LOG"
grep -Fx 'mise:--no-config x go@1.26.5 -- env CGO_ENABLED=0 GOTOOLCHAIN=local go run -trimpath github.com/buildkite/buildkite-gha/cmd/buildkite-gha@abcdef0123456789abcdef0123456789abcdef01 upload --runtime-queue hosted .github/workflows/ci.yml' "$MOCK_LOG"
grep -Fx 'mise:--no-config x go@1.26.5 -- env CGO_ENABLED=0 GOTOOLCHAIN=local go run -trimpath github.com/buildkite/buildkite-gha/cmd/buildkite-gha@abcdef0123456789abcdef0123456789abcdef01 upload .github/workflows/ci.yml' "$MOCK_LOG"
! grep -q -- '--runtime-queue' "$MOCK_LOG"
! grep -q -- '--private-checkout' "$MOCK_LOG"
}

@test "rejects invalid or ambiguous source configuration without running importer" {
Expand Down Expand Up @@ -362,7 +346,7 @@ EOF
run "$REPO/hooks/command"
[ "$status" -eq 0 ] || { echo "$output"; false; }
[[ "$output" == *"cache '$BUILDKITE_GITHUB_ACTIONS_PLUGIN_CACHE_ROOT' is unavailable; using a temporary cache"* ]]
grep -Fx 'upload --runtime-queue hosted .github/workflows/ci.yml' "$MOCK_LOG"
grep -Fx 'upload .github/workflows/ci.yml' "$MOCK_LOG"
}

@test "continues when the verified CLI archive cannot be cached" {
Expand All @@ -379,7 +363,7 @@ EOF
run "$REPO/hooks/command"
[ "$status" -eq 0 ] || { echo "$output"; false; }
[[ "$output" == *"continuing without caching"* ]]
grep -Fx 'upload --runtime-queue hosted .github/workflows/ci.yml' "$MOCK_LOG"
grep -Fx 'upload .github/workflows/ci.yml' "$MOCK_LOG"
[ ! -e "$BUILDKITE_GITHUB_ACTIONS_PLUGIN_CACHE_ROOT/v0.6.0/Linux_x86_64" ]
}

Expand All @@ -400,7 +384,7 @@ EOF
run "$REPO/hooks/command"
[ "$status" -eq 0 ] || { echo "$output"; false; }
grep -Fx 'https://github.com/buildkite/buildkite-gha/releases/download/v0.6.0/buildkite-gha_Linux_x86_64.tar.gz' "$MOCK_LOG"
grep -Fx 'upload --runtime-queue hosted .github/workflows/ci.yml' "$MOCK_LOG"
grep -Fx 'upload .github/workflows/ci.yml' "$MOCK_LOG"
}

@test "concurrent installs converge on one valid cache" {
Expand All @@ -414,7 +398,7 @@ EOF
[ -f "$cached_archive" ]
expected="$(awk '$2 == "buildkite-gha_Linux_x86_64.tar.gz" { print $1 }' "$TMP/checksums.txt")"
[ "$(/usr/bin/sha256sum "$cached_archive" | awk '{ print $1 }')" = "$expected" ]
[ "$(grep -c '^upload --runtime-queue hosted ' "$MOCK_LOG")" -eq 2 ]
[ "$(grep -c '^upload .github/workflows/ci.yml$' "$MOCK_LOG")" -eq 2 ]
}

@test "propagates importer failure and never runs importer after install failure" {
Expand Down