diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9213b8d..3fc71ce 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,94 +1,91 @@ -# Generated by labkit (python -m labkit gen). Do not edit by hand. -# Edit labs//publish.config.json and regenerate; drift fails `labkit doctor`. -# Install into operatorstack/interlock at .github/workflows/release.yml (bootstrap step). -name: Release Interlock +# interlock release — rendered by distribution/render.mjs; hand-install into operatorstack/interlock's +# .github/workflows/ (control plane can't be projected). Builds prebuilt binaries + bindings and +# publishes them to GCP Artifact Registry (fronted by get.operatorstack.systems). No GitHub Releases. +# +# The AR project/location/repos come from repo VARIABLES (not hardcoded) so the private project id +# never lands in public source. Set on operatorstack/interlock: +# vars: WIF_PROVIDER, DEPLOYER_SA_EMAIL (the pkg-deployer SA), +# AR_PROJECT, AR_LOCATION, AR_GENERIC_REPO, AR_NPM_REPO, AR_PYTHON_REPO +name: release-interlock on: push: - branches: [main] + tags: ["v*"] workflow_dispatch: - inputs: - bump: - description: Version bump for a manual release - type: choice - options: [patch, minor, major] - default: patch permissions: - contents: write - pull-requests: read - -concurrency: - group: release-interlock - cancel-in-progress: false + contents: read + id-token: write # Workload Identity Federation — no keys + attestations: write # build provenance jobs: release: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: { fetch-depth: 0 } + - uses: actions/setup-go@v5 + with: { go-version: "stable" } + - uses: actions/setup-node@v4 + with: { node-version: "22" } + - uses: actions/setup-python@v5 + with: { python-version: "3.11" } + + - id: auth + uses: google-github-actions/auth@v2 + with: + workload_identity_provider: ${{ vars.WIF_PROVIDER }} + service_account: ${{ vars.DEPLOYER_SA_EMAIL }} + - uses: google-github-actions/setup-gcloud@v2 + + - name: build binaries + checksums + SBOM + uses: goreleaser/goreleaser-action@v6 + with: + version: "~> v2" + args: release --clean + + - name: attest build provenance + uses: actions/attest-build-provenance@v2 with: - fetch-depth: 0 - - name: Resolve release policy - id: policy - env: - GH_TOKEN: ${{ github.token }} - MANUAL_BUMP: ${{ inputs.bump }} - shell: bash + subject-path: "dist/interlock_*.tar.gz, dist/interlock_*.zip, dist/checksums.txt" + + - name: publish binaries -> Artifact Registry (generic) run: | - bump="${MANUAL_BUMP:-patch}" - skip="false" - if [[ "${GITHUB_EVENT_NAME}" == "push" ]]; then - labels="$(gh api \ - -H 'Accept: application/vnd.github+json' \ - "/repos/${GITHUB_REPOSITORY}/commits/${GITHUB_SHA}/pulls" \ - --jq '.[0].labels[].name' 2>/dev/null || true)" - if grep -qx 'skip-release' <<<"$labels"; then skip="true"; fi - if grep -qx 'major' <<<"$labels"; then - bump="major" - elif grep -qx 'minor' <<<"$labels"; then - bump="minor" - fi - fi - echo "bump=$bump" >> "$GITHUB_OUTPUT" - echo "skip=$skip" >> "$GITHUB_OUTPUT" - - name: Compute version - if: steps.policy.outputs.skip != 'true' - id: version - env: - BUMP: ${{ steps.policy.outputs.bump }} - shell: bash + set -euo pipefail + VER="${GITHUB_REF_NAME}" + for f in dist/interlock_*.tar.gz dist/interlock_*.zip dist/checksums.txt; do + [ -e "$f" ] || continue + gcloud artifacts generic upload \ + --project="${{ vars.AR_PROJECT }}" --location="${{ vars.AR_LOCATION }}" \ + --repository="${{ vars.AR_GENERIC_REPO }}" \ + --package=interlock --version="${VER}" --source="$f" + done + + - name: update latest channel manifest run: | - latest="$(git tag --list 'v[0-9]*' --sort=-v:refname | head -n 1)" - if [[ -z "$latest" ]]; then - next="v0.1.0" - else - raw="${latest#v}" - IFS=. read -r major minor patch <<<"$raw" - case "$BUMP" in - major) major=$((major + 1)); minor=0; patch=0 ;; - minor) minor=$((minor + 1)); patch=0 ;; - patch) patch=$((patch + 1)) ;; - *) echo "Invalid bump: $BUMP" >&2; exit 2 ;; - esac - next="v${major}.${minor}.${patch}" - fi - echo "version=$next" >> "$GITHUB_OUTPUT" - - name: Publish release - if: steps.policy.outputs.skip != 'true' - env: - GH_TOKEN: ${{ github.token }} - VERSION: ${{ steps.version.outputs.version }} - shell: bash + set -euo pipefail + printf '{"version":"%s"}\n' "${GITHUB_REF_NAME}" > latest.json + gcloud artifacts generic upload \ + --project="${{ vars.AR_PROJECT }}" --location="${{ vars.AR_LOCATION }}" \ + --repository="${{ vars.AR_GENERIC_REPO }}" \ + --package=interlock-channel --version=latest --source=latest.json + + - name: publish npm binding (@operatorstack/interlock) + working-directory: clients/typescript + run: | + set -euo pipefail + gcloud artifacts print-settings npm \ + --project="${{ vars.AR_PROJECT }}" --location="${{ vars.AR_LOCATION }}" \ + --repository="${{ vars.AR_NPM_REPO }}" --scope=@operatorstack > .npmrc + npx -y google-artifactregistry-auth .npmrc + npm publish + + - name: publish python binding (interlock-protocol) + working-directory: clients/python run: | - if git rev-parse --verify --quiet "refs/tags/$VERSION"; then - echo "Tag $VERSION already exists; nothing to release." - exit 0 - fi - git tag "$VERSION" "$GITHUB_SHA" - git push origin "$VERSION" - gh release create "$VERSION" \ - --repo "$GITHUB_REPOSITORY" \ - --title "$VERSION" \ - --generate-notes \ - --verify-tag + set -euo pipefail + pip install --quiet build twine keyrings.google-artifactregistry-auth + python -m build + twine upload \ + --repository-url "https://${{ vars.AR_LOCATION }}-python.pkg.dev/${{ vars.AR_PROJECT }}/${{ vars.AR_PYTHON_REPO }}/" \ + dist/*