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
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ jobs:
.github/workflows/slash-command-dispatch.yml
.github/workflows/ai-review.yml
.github/workflows/release-please.yml
.github/workflows/release-proposal.yml
.github/workflows/release-acceptance.yml
.github/workflows/release-publish.yml
version: 1.29.0
min-severity: high
min-confidence: high
Expand Down
108 changes: 108 additions & 0 deletions .github/workflows/release-acceptance.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
name: Release acceptance

# Local calls resolve at the caller's commit. There is deliberately no push,
# workflow_run, tag, or standalone dispatch route and no inherited secrets.
on:
workflow_call:
inputs:
target_sha:
required: true
type: string
ci_run_id:
required: true
type: string
ci_run_attempt:
required: true
type: string
operation:
required: true
type: string
release_pr:
default: ''
type: string
recover_from:
default: ''
type: string
outputs:
artifact_id:
value: ${{ jobs.accept.outputs.artifact_id }}
artifact_digest:
value: ${{ jobs.accept.outputs.artifact_digest }}

permissions:
contents: read
actions: read # Inspect exact runs, attempts, artifacts and environment policy.
checks: read # Verify current protected check results and App identities.
pull-requests: read # Verify the release PR and its exact merge commit.

env:
TARGET_SHA: ${{ inputs.target_sha }}
CI_RUN_ID: ${{ inputs.ci_run_id }}
CI_RUN_ATTEMPT: ${{ inputs.ci_run_attempt }}
OPERATION: ${{ inputs.operation }}
RELEASE_PR: ${{ inputs.release_pr }}
RECOVER_FROM: ${{ inputs.recover_from }}
PUBLICATION_ENABLED: ${{ vars.RELEASE_PUBLICATION_ENABLED }}
MAIN_FREEZE_ATTESTATION: ${{ vars.RELEASE_MAIN_FREEZE }}

jobs:
preflight:
name: Check current main and operator configuration
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
with:
ref: ${{ github.sha }}
persist-credentials: false
- run: python3 -m scripts.release_gate
env:
GH_TOKEN: ${{ github.token }}

accept:
name: Build and accept distributions
needs: preflight
runs-on: ubuntu-24.04
timeout-minutes: 90
outputs:
artifact_id: ${{ steps.upload.outputs.artifact-id }}
artifact_digest: ${{ steps.upload.outputs.artifact-digest }}
services:
postgres:
image: postgres:16.14@sha256:95206741a5b214807675e14165369d05b93a9cf692223b616d07cca227e74b0b
env:
POSTGRES_HOST_AUTH_METHOD: trust
POSTGRES_DB: adcp_release_acceptance
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 5s
--health-timeout 5s
--health-retries 10
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
with:
ref: ${{ github.sha }}
persist-credentials: false
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: '3.10'
- name: Install build tooling
run: python -m pip install build==1.2.2.post1 setuptools==80.9.0 wheel==0.45.1
- name: Build and install the exact candidates outside the checkout
run: python -m scripts.release_artifacts build candidate
env:
GH_TOKEN: ${{ github.token }}
ADCP_PG_TEST_URL: postgresql://postgres@localhost:5432/adcp_release_acceptance
- name: Retain immutable distributions and acceptance evidence
id: upload
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: release-acceptance-${{ github.run_id }}-1
path: candidate/
if-no-files-found: error
overwrite: false
archive: true
compression-level: 0
retention-days: 30
122 changes: 16 additions & 106 deletions .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
@@ -1,114 +1,24 @@
name: Release Please

# Workflow 204238826 is deliberately retired, not renamed or re-enabled.
# Historical runs retain their old code: see docs/releasing.md before enabling
# the NEW publisher. This tombstone cannot make historical runs safe.
on:
workflow_dispatch:
inputs:
publish:
description: Build and publish the current pyproject version to PyPI
type: boolean
default: false
push:
branches:
- main

jobs:
release-please:
permissions:
contents: write
pull-requests: write
runs-on: ubuntu-latest
steps:
# Release Please must write release PR branches with an installation token,
# not the default GITHUB_TOKEN. GITHUB_TOKEN-authored pushes do not trigger
# downstream pull_request workflows, so protected release PRs can sit
# blocked waiting for CI/IPR contexts that never start.
- name: Mint App token
id: app-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
app-id: ${{ secrets.IPR_APP_ID }}
private-key: ${{ secrets.IPR_APP_PRIVATE_KEY }}
permission-contents: write
permission-pull-requests: write

- uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5
id: release
if: ${{ github.event_name != 'workflow_dispatch' || inputs.publish != true }}
with:
token: ${{ steps.app-token.outputs.token }}
config-file: release-please-config.json
manifest-file: .release-please-manifest.json

- name: Checkout release PR
if: ${{ steps.release.outputs.prs_created == 'true' }}
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
with:
fetch-depth: 0
token: ${{ steps.app-token.outputs.token }}

- name: Normalize release PR pyproject prerelease version
if: ${{ steps.release.outputs.prs_created == 'true' }}
env:
REF_NAME: ${{ github.ref_name }}
run: |
set -euo pipefail

BASE_RELEASE_PR_BRANCH="release-please--branches--${REF_NAME}"
COMPONENT_RELEASE_PR_BRANCH="${BASE_RELEASE_PR_BRANCH}--components--adcp"

if git ls-remote --exit-code --heads origin "${COMPONENT_RELEASE_PR_BRANCH}" >/dev/null 2>&1; then
RELEASE_PR_BRANCH="${COMPONENT_RELEASE_PR_BRANCH}"
elif git ls-remote --exit-code --heads origin "${BASE_RELEASE_PR_BRANCH}" >/dev/null 2>&1; then
RELEASE_PR_BRANCH="${BASE_RELEASE_PR_BRANCH}"
else
echo "No release PR branch found to normalize"
exit 0
fi

git fetch origin "${RELEASE_PR_BRANCH}:${RELEASE_PR_BRANCH}"
git switch "${RELEASE_PR_BRANCH}"
permissions: {}

python3 scripts/normalize_pyproject_prerelease.py pyproject.toml
concurrency:
group: retired-release-please
cancel-in-progress: false

if git diff --quiet -- pyproject.toml; then
echo "pyproject.toml already uses a PEP 440-compatible version"
exit 0
fi

git config user.name "aao-ipr-bot[bot]"
git config user.email "aao-ipr-bot[bot]@users.noreply.github.com"
git add pyproject.toml
git commit -m "chore: normalize prerelease version to PEP 440"
git push origin "HEAD:${RELEASE_PR_BRANCH}"

# Publish to PyPI when a release is created
- name: Checkout
if: ${{ steps.release.outputs.release_created == 'true' || (github.event_name == 'workflow_dispatch' && inputs.publish == true) }}
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0

- name: Setup Python
if: ${{ steps.release.outputs.release_created == 'true' || (github.event_name == 'workflow_dispatch' && inputs.publish == true) }}
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: '3.10'

- name: Install build dependencies
if: ${{ steps.release.outputs.release_created == 'true' || (github.event_name == 'workflow_dispatch' && inputs.publish == true) }}
jobs:
retired:
name: Refuse the retired workflow
runs-on: ubuntu-24.04
timeout-minutes: 1
steps:
- name: Refuse the retired publication entry point
run: |
python -m pip install --upgrade pip
pip install build twine

- name: Bundle schemas
if: ${{ steps.release.outputs.release_created == 'true' || (github.event_name == 'workflow_dispatch' && inputs.publish == true) }}
run: python scripts/bundle_schemas.py

- name: Build package
if: ${{ steps.release.outputs.release_created == 'true' || (github.event_name == 'workflow_dispatch' && inputs.publish == true) }}
run: python -m build

- name: Publish to PyPI
if: ${{ steps.release.outputs.release_created == 'true' || (github.event_name == 'workflow_dispatch' && inputs.publish == true) }}
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPY_API_TOKEN }}
run: twine upload dist/*
echo 'Release Please is retired. Use the guarded proposal or publication workflow.' >&2
exit 1
102 changes: 102 additions & 0 deletions .github/workflows/release-proposal.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
name: Release proposal

on:
workflow_dispatch:
inputs:
target_sha:
description: Full current-main commit SHA to accept before proposing
required: true
type: string
ci_run_id:
description: Successful main-push CI run for exactly target_sha
required: true
type: string
ci_run_attempt:
description: Exact successful CI attempt
required: true
default: '1'
type: string

permissions: {}

concurrency:
group: adcp-release-main
cancel-in-progress: false

jobs:
acceptance:
permissions:
contents: read
actions: read # Inspect exact runs, attempts, artifacts and environment policy.
checks: read # Verify current protected check results and App identities.
pull-requests: read # Verify the release PR and its exact merge commit.
uses: ./.github/workflows/release-acceptance.yml
with:
target_sha: ${{ inputs.target_sha }}
ci_run_id: ${{ inputs.ci_run_id }}
ci_run_attempt: ${{ inputs.ci_run_attempt }}
operation: proposal

propose:
name: Propose the accepted release
needs: acceptance
if: >-
github.run_attempt == 1 && github.ref == 'refs/heads/main' &&
github.sha == inputs.target_sha && github.workflow_sha == inputs.target_sha
environment: release-proposal
runs-on: ubuntu-24.04
timeout-minutes: 15
permissions:
contents: read
actions: read # Inspect exact runs, attempts, artifacts and environment policy.
checks: read # Verify current protected check results and App identities.
pull-requests: read # Verify the release PR and its exact merge commit.
env:
TARGET_SHA: ${{ inputs.target_sha }}
CI_RUN_ID: ${{ inputs.ci_run_id }}
CI_RUN_ATTEMPT: ${{ inputs.ci_run_attempt }}
OPERATION: proposal
MAIN_FREEZE_ATTESTATION: ${{ vars.RELEASE_MAIN_FREEZE }}
ARTIFACT_ID: ${{ needs.acceptance.outputs.artifact_id }}
ARTIFACT_DIGEST: ${{ needs.acceptance.outputs.artifact_digest }}
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
with:
ref: ${{ github.sha }}
persist-credentials: false
- name: Reapply acceptance after environment approval
run: python3 -m scripts.release_artifacts verify accepted-candidate
env:
GH_TOKEN: ${{ github.token }}
# A dedicated environment-scoped key, never the legacy IPR key. An App
# token is necessary so the proposed branch triggers protected PR CI.
- name: Mint release PR App token
id: app-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
app-id: ${{ vars.RELEASE_PROPOSAL_APP_ID }}
private-key: ${{ secrets.RELEASE_PROPOSAL_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
repositories: adcp-client-python
permission-contents: write # Write the normal release PR branch.
permission-pull-requests: write # Create/update its PR and pending label.
- name: Recheck immediately before the proposal
run: python3 -m scripts.release_gate
env:
GH_TOKEN: ${{ github.token }}
- name: Create or update the normal release PR
id: release
uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5
with:
token: ${{ steps.app-token.outputs.token }}
target-branch: main
config-file: release-please-config.json
manifest-file: .release-please-manifest.json
skip-github-release: true
skip-github-pull-request: false
- name: Normalize the proposed PEP 440 version
run: python3 -m scripts.release_gate --normalize-proposal
env:
GH_TOKEN: ${{ github.token }}
PROPOSAL_TOKEN: ${{ steps.app-token.outputs.token }}
PROPOSAL_PRS: ${{ steps.release.outputs.prs }}
Loading
Loading