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
37 changes: 37 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ on:
permissions:
contents: read

env:
RELEASE_BRANCH: production

concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
Expand All @@ -21,6 +24,8 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v7
with:
fetch-depth: 0

- name: Install yq
shell: bash
Expand All @@ -37,6 +42,38 @@ jobs:
shell: bash
run: make test

- name: Require a version bump for runtime changes
if: github.event_name == 'pull_request' && github.base_ref == env.RELEASE_BRANCH
shell: bash
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
set -euo pipefail

if git diff --quiet "$BASE_SHA" "$HEAD_SHA" -- action.yml bin; then
echo "No action runtime change detected."
exit 0
fi

base_version="$(git show "$BASE_SHA:package.json" | jq -r '.version // empty')"
head_version="$(jq -r '.version // empty' package.json)"
if [[ ! "$base_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ || ! "$head_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "::error title=Invalid release version::Expected semantic versions in package.json."
exit 1
fi

IFS=. read -r base_major base_minor base_patch <<< "$base_version"
IFS=. read -r head_major head_minor head_patch <<< "$head_version"
if (( head_major < base_major ||
(head_major == base_major && head_minor < base_minor) ||
(head_major == base_major && head_minor == base_minor && head_patch <= base_patch) )); then
echo "::error title=Release version required::Runtime changes to action.yml or bin/ must increase package.json from $base_version."
exit 1
fi

echo "Runtime change is prepared for release: $base_version -> $head_version"

lint-workflows:
name: lint GitHub Actions workflows
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ jobs:
payload="$(jq -nc \
--arg tag "$RELEASE_TAG" \
--arg url "$RELEASE_URL" \
'{text: (":rocket: Rabbit Automation Action *" + $tag + "* is published.\n" + $url + "\n\nNext:\n• Wait for *Verify release* to pass.\n• Verify Marketplace shows this version and current metadata/README.\n• Run a plan-only non-production caller canary with `@production`, then move `v1`.")}')"
'{text: (":rocket: Rabbit Automation Action *" + $tag + "* is published.\n" + $url + "\n\nNext:\n• Wait for *Verify release* to pass.\n• Verify Marketplace shows this version and current metadata/README.\n• Run a plan-only non-production caller canary with `@" + $tag + "`, then move `v1`.")}')"
curl --fail-with-body --silent --show-error \
--request POST \
--header 'Content-type: application/json' \
Expand Down
4 changes: 4 additions & 0 deletions .rabbit/repo.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ branches:
rules: {}
- name: fix/gcp-credential-mount
rules: {}
- name: fix/stage-wif-credential-copy
rules: {}
- name: fix/wif-credential-readable
rules: {}
- name: infra-templates
rules: {}
- name: lifecycle-action-integration
Expand Down
7 changes: 4 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ Run `make test` before opening or updating a pull request.

## Releases

Keep public action changes backward compatible within `v1`. Bump the version
in `package.json` and add its matching immutable `v1.x.y` entry to
`CHANGELOG.md` with each release-worthy change; the production release workflow
Keep public action changes backward compatible within `v1`. Any runtime change
to `action.yml` or `bin/` is release-worthy. The pull request targeting
`production` must bump the version in `package.json` and add its matching
immutable `v1.x.y` entry to `CHANGELOG.md`. The production release workflow
publishes it after validation and notifies `#rabbit-support` through its
dedicated repository secret. Confirm the Marketplace UI publication, then move
the `v1` tag only after caller canary validation. Follow
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
All notable changes to this action are recorded here. Versions follow semantic
versioning; callers should normally use the maintained `v1` major tag.

## v1.0.5 - 2026-08-29

- Mount a temporary private copy of Google Workload Identity credentials so the
non-root R2A container can read them without changing the caller file's mode.

## v1.0.4 - 2026-08-24

- Clarified the one-time GitHub Marketplace setup and the checks required for
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,7 @@ The workflow dispatch inputs provide safe manual control:

## Authentication and state ownership

The caller workflow owns cloud authentication. Authenticate with Google Cloud before invoking the action; the action mounts the resulting `GOOGLE_APPLICATION_CREDENTIALS` file read-only into the R2A container and never copies it into the workspace. Configure AWS credentials in the caller when the configuration uses AWS; the action forwards the resulting AWS session variables to R2A.
The caller workflow owns cloud authentication. Authenticate with Google Cloud before invoking the action; the action copies the credential file into a private runner directory, mounts that copy read-only for R2A, then removes it when the step exits. It never changes the caller credential file or copies credentials into the workspace. Configure AWS credentials in the caller when the configuration uses AWS; the action forwards the resulting AWS session variables to R2A.

The optional state-backend inputs are passed through to the IaC engine. Omit them to retain its existing GCS default; provide the backend type, configuration, and state-path key only when the selected backend requires an override.

Expand Down
15 changes: 14 additions & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -551,7 +551,20 @@ runs:
echo "::error title=GCP credentials unavailable::Resolved credentials file is no longer readable."
exit 1
fi
gcp_mount=(-v "$GCP_CREDENTIALS_PATH:/tmp/gcp-credentials.json:ro" -e "GOOGLE_APPLICATION_CREDENTIALS=/tmp/gcp-credentials.json")
# google-github-actions/auth writes the credential file 0600 and the
# R2A container runs as the non-root udx user. Keep the caller's
# credential file private and mount a world-readable copy from a
# runner-private directory instead.
gcp_credentials_dir="$(mktemp -d "${RUNNER_TEMP:-/tmp}/rabbit-gcp-credentials.XXXXXX")"
gcp_credentials_copy="$gcp_credentials_dir/gcp-credentials.json"
cleanup_gcp_credentials() {
local status=$?
rm -rf "$gcp_credentials_dir" || true
return "$status"
}
trap cleanup_gcp_credentials EXIT
install -m 0644 -- "$GCP_CREDENTIALS_PATH" "$gcp_credentials_copy"
gcp_mount=(-v "$gcp_credentials_copy:/tmp/gcp-credentials.json:ro" -e "GOOGLE_APPLICATION_CREDENTIALS=/tmp/gcp-credentials.json")
fi

echo "🐰 Running Rabbit Automation Action..."
Expand Down
19 changes: 12 additions & 7 deletions docs/releasing.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ The action is released from `production`. Patch releases are immutable

## Prepare a release

1. Bump `package.json` to the next semantic version and add matching concise
user-facing notes at the top of `CHANGELOG.md` in the pull request that
changes action behavior.
1. In the pull request targeting `production`, bump `package.json` to the next
semantic version and add matching concise user-facing notes at the top of
`CHANGELOG.md` for the action behavior changes in that release.
2. Merge the focused, reviewed pull request into `production`.

CI rejects a production-targeting pull request that changes `action.yml` or
`bin/` unless its `package.json` version increases. `make test` also requires
the first semantic heading in `CHANGELOG.md` to match that version.

The `Publish release` workflow runs after every `production` push. It does
nothing unless that push changes the `package.json` version; then it runs
`make test`, refuses to reuse an existing tag, and publishes that GitHub
Expand Down Expand Up @@ -49,13 +53,14 @@ Marketplace listing check.

## Promote callers

1. In a caller repository's non-production environment, run a plan using
`udx/github-rabbit-action@production`. This caller canary proves the exact
1. After the semantic release is published, in a caller repository's
non-production environment run a plan using its immutable tag, for example
`udx/github-rabbit-action@v1.0.5`. This caller canary proves the exact
release commit works in a real consumer workflow; it must not apply
infrastructure.
2. Move the `v1` tag to the tested immutable release commit.
3. Confirm `v1` and the patch tag resolve to the same commit with
`git ls-remote --tags origin 'v1*'`.
3. Compare the `v1` and new patch-tag SHAs to confirm they resolve to the same
commit: `git ls-remote --tags origin refs/tags/v1 refs/tags/v1.0.5`.
4. Update reusable workflows and callers from `@production` to `@v1`.
5. Run a non-production caller plan using `@v1` before merging the consumer
change.
Expand Down
6 changes: 5 additions & 1 deletion docs/validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,8 @@ rabbit.ci

## CI

The `ci` workflow runs on pull requests and pushes to `production` and `lifecycle-action-integration`. It installs a pinned `yq` binary and runs `make test`.
The `ci` workflow runs on pull requests and pushes to `production`. It installs
a pinned `yq` binary, runs `make test`, and uses `actionlint` for GitHub Actions
workflow linting. On pull requests targeting `production`, validation also
requires a semantic `package.json` version increase when `action.yml` or `bin/`
changes.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@udx/github-rabbit-action",
"version": "1.0.4",
"version": "1.0.5",
"private": true,
"description": "Rabbit Automation Action release manifest",
"license": "GPL-2.0-only",
Expand Down
4 changes: 4 additions & 0 deletions tests/run-merge-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ assert_eq "$(yq -r '.runs.steps[] | select(.name == "Upload terraform plans") |
assert_eq "$(yq -r '[.runs.steps[] | select(.uses == "google-github-actions/auth@v3" or .uses == "aws-actions/configure-aws-credentials@v6")] | length' "$PROJECT_ROOT/action.yml")" "0" "Action does not configure cloud credentials"
assert_eq "$(grep -c 'Authenticate with Google Cloud before invoking github-rabbit-action' "$PROJECT_ROOT/action.yml")" "1" "Action requires caller-provided GCP credentials"
assert_eq "$(grep -c 'AWS credentials configured by the caller workflow' "$PROJECT_ROOT/action.yml")" "1" "Action forwards caller AWS credentials"
assert_eq "$(grep -c 'mktemp -d "\${RUNNER_TEMP:-/tmp}/rabbit-gcp-credentials.XXXXXX"' "$PROJECT_ROOT/action.yml")" "1" "Action stages GCP credentials in a private temporary directory"
assert_eq "$(grep -c 'install -m 0644 -- "\$GCP_CREDENTIALS_PATH" "\$gcp_credentials_copy"' "$PROJECT_ROOT/action.yml")" "1" "Action mounts a readable credential copy"
assert_eq "$(grep -c 'rm -rf "\$gcp_credentials_dir"' "$PROJECT_ROOT/action.yml")" "1" "Action removes the staged GCP credential copy"
assert_eq "$(grep -c 'chmod 0644 "\$GCP_CREDENTIALS_PATH"' "$PROJECT_ROOT/action.yml" || true)" "0" "Action leaves the caller GCP credential mode unchanged"

write_yaml "$SOURCE/production/10-base.yaml" 'services:
- module: test-module
Expand Down