Skip to content

Publish 2.0.0-beta.13@82f752135ab6f03c75d121721b7c1f8e8cb30d9c from direct #123

Publish 2.0.0-beta.13@82f752135ab6f03c75d121721b7c1f8e8cb30d9c from direct

Publish 2.0.0-beta.13@82f752135ab6f03c75d121721b7c1f8e8cb30d9c from direct #123

Workflow file for this run

name: Publish to PyPI
run-name: >-
${{ (github.event_name == 'push' || inputs.publish) && 'Publish' || 'Build' }}
${{ inputs.release_tag || github.ref_name }}@${{ inputs.release_commit || github.sha }}
from ${{ inputs.release_plan || 'direct' }}
on:
push:
tags:
- '[0-9]+.[0-9]+.[0-9]+*'
workflow_dispatch:
inputs:
release_tag:
description: 'Existing immutable SDK release tag; empty permits a build-only run'
required: false
type: string
default: ''
release_commit:
description: 'Expected commit for a release-plan publication'
required: false
type: string
default: ''
release_plan:
description: 'Immutable release-plan tag initiating this recovery run'
required: false
type: string
default: 'direct'
publish:
description: 'Publish the exact release tag to PyPI'
required: false
type: boolean
default: false
dry_run:
description: 'Legacy TestPyPI dry-run guard'
required: false
type: boolean
default: true
permissions:
contents: read
concurrency:
group: release-${{ inputs.release_tag || github.ref_name }}
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
outputs:
artifact-digest: ${{ steps.privileged-handoff.outputs.artifact-digest }}
artifact-id: ${{ steps.privileged-handoff.outputs.artifact-id }}
release_tag: ${{ steps.release_source.outputs.tag }}
release_commit: ${{ steps.release_source.outputs.commit }}
source-run-attempt: ${{ github.run_attempt }}
source-run-id: ${{ github.run_id }}
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
fetch-depth: 0
ref: >-
${{ github.event_name == 'workflow_dispatch' && inputs.release_tag != ''
&& format('refs/tags/{0}', inputs.release_tag) || github.ref }}
- name: Resolve exact release identity
id: release_source
env:
REQUESTED_TAG: ${{ github.event_name == 'workflow_dispatch' && inputs.release_tag || github.ref_name }}
REQUESTED_COMMIT: ${{ github.event_name == 'workflow_dispatch' && inputs.release_commit || '' }}
PUBLISH_REQUESTED: ${{ github.event_name == 'push' || inputs.publish }}
run: |
set -euo pipefail
package_version="$(python -c 'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])')"
head_commit="$(git rev-parse HEAD)"
if [ "$PUBLISH_REQUESTED" = true ]; then
if [[ ! "$REQUESTED_TAG" =~ ^[0-9]+\.[0-9]+\.[0-9]+([-+][0-9A-Za-z.-]+)?$ ]]; then
printf 'release tag must be an exact SDK SemVer: %s\n' "$REQUESTED_TAG" >&2
exit 1
fi
if [ "$REQUESTED_TAG" != "$package_version" ]; then
printf 'release tag %s does not match package version %s\n' "$REQUESTED_TAG" "$package_version" >&2
exit 1
fi
tag_commit="$(git rev-list -n 1 "$REQUESTED_TAG")"
if [ "$tag_commit" != "$head_commit" ]; then
printf 'release tag %s points to %s, not checkout commit %s\n' \
"$REQUESTED_TAG" "$tag_commit" "$head_commit" >&2
exit 1
fi
if [ -n "$REQUESTED_COMMIT" ]; then
if [[ ! "$REQUESTED_COMMIT" =~ ^[0-9a-f]{40}$ ]]; then
printf 'release commit must be an exact Git object ID: %s\n' "$REQUESTED_COMMIT" >&2
exit 1
fi
if [ "$tag_commit" != "$REQUESTED_COMMIT" ]; then
printf 'release tag %s points to %s, not requested commit %s\n' \
"$REQUESTED_TAG" "$tag_commit" "$REQUESTED_COMMIT" >&2
exit 1
fi
fi
else
REQUESTED_TAG="$package_version"
fi
{
printf 'tag=%s\n' "$REQUESTED_TAG"
printf 'commit=%s\n' "$head_commit"
} >> "$GITHUB_OUTPUT"
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: "3.12"
- name: Install build tools
run: pip install build twine
- name: Build package
run: python -m build
- name: Check package
run: twine check dist/*
- name: Smoke test built package
run: python scripts/smoke-built-package.py
- name: Upload artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: dist
path: dist/
- name: Package the privileged PyPI handoff as one immutable file
run: tar -cf dist-handoff.tar -C dist .
- name: Bind the privileged PyPI handoff identity and digest
id: privileged-handoff
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
archive: false
if-no-files-found: error
path: dist-handoff.tar
publish:
needs: build
runs-on: ubuntu-latest
if: >-
(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')) ||
(github.event_name == 'workflow_dispatch' &&
github.ref == 'refs/heads/main' && inputs.publish)
environment: pypi
permissions:
actions: read
contents: write
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
fetch-depth: 0
persist-credentials: false
ref: ${{ github.sha }}
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
artifact-ids: ${{ needs.build.outputs.artifact-id }}
digest-mismatch: error
github-token: ${{ github.token }}
path: isolated-python-dist
repository: ${{ github.repository }}
run-id: ${{ needs.build.outputs.source-run-id }}
- name: Validate the exact producer artifact before use
env:
ARTIFACT_DIRECTORY: isolated-python-dist
EXPECTED_ARTIFACT_DIGEST: ${{ needs.build.outputs.artifact-digest }}
EXPECTED_ARTIFACT_ID: ${{ needs.build.outputs.artifact-id }}
EXPECTED_SOURCE_RUN_ATTEMPT: ${{ needs.build.outputs.source-run-attempt }}
EXPECTED_SOURCE_RUN_ID: ${{ needs.build.outputs.source-run-id }}
run: |
set -euo pipefail
if [[ ! "$EXPECTED_ARTIFACT_DIGEST" =~ ^[0-9a-f]{64}$ ]]; then
printf 'producer artifact digest is not an exact SHA-256 digest\n' >&2
exit 1
fi
for identity in "$EXPECTED_ARTIFACT_ID" "$EXPECTED_SOURCE_RUN_ID" "$EXPECTED_SOURCE_RUN_ATTEMPT"; do
if [[ ! "$identity" =~ ^[1-9][0-9]*$ ]]; then
printf 'producer artifact identity is invalid\n' >&2
exit 1
fi
done
if [[ ! "$ARTIFACT_DIRECTORY" =~ ^isolated-[a-z0-9][a-z0-9._-]*$ ]]; then
printf 'artifact validation directory is unsafe\n' >&2
exit 1
fi
mapfile -d '' entries < <(
/usr/bin/find "$ARTIFACT_DIRECTORY" -mindepth 1 -maxdepth 1 -print0
)
if [ "${#entries[@]}" -ne 1 ] || [ ! -f "${entries[0]}" ] || [ -L "${entries[0]}" ]; then
printf 'artifact handoff must contain exactly one regular file\n' >&2
exit 1
fi
observed_digest="$(/usr/bin/sha256sum "${entries[0]}")"
observed_digest="${observed_digest%% *}"
if [ "$observed_digest" != "$EXPECTED_ARTIFACT_DIGEST" ]; then
printf 'artifact digest mismatch: expected %s, got %s\n' \
"$EXPECTED_ARTIFACT_DIGEST" "$observed_digest" >&2
exit 1
fi
printf 'validated artifact %s from run %s attempt %s\n' \
"$EXPECTED_ARTIFACT_ID" "$EXPECTED_SOURCE_RUN_ID" "$EXPECTED_SOURCE_RUN_ATTEMPT"
- name: Extract the validated PyPI handoff
run: |
mkdir dist
tar -xf isolated-python-dist/dist-handoff.tar -C dist
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@ba38be9e461d3875417946c167d0b5f3d385a247 # release/v1
with:
password: ${{ secrets.PYPI_TOKEN }}
print-hash: true
skip-existing: true
- name: Create the source GitHub Release
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ needs.build.outputs.release_tag }}
run: |
if ! gh release view "$RELEASE_TAG" >/dev/null 2>&1; then
arguments=(--verify-tag --generate-notes --title "$RELEASE_TAG")
if [[ "$RELEASE_TAG" == *-* ]]; then
arguments+=(--prerelease)
fi
gh release create "$RELEASE_TAG" "${arguments[@]}"
fi
verify-docs-release-audit:
needs: [build, publish]
runs-on: ubuntu-latest
if: >-
(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')) ||
(github.event_name == 'workflow_dispatch' && inputs.publish)
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
ref: ${{ needs.build.outputs.release_tag }}
- name: Verify live docs release audit after PyPI publish
env:
DOCS_RELEASE_AUDIT_ARTIFACT: sdk-python
DOCS_RELEASE_AUDIT_VERSION: ${{ needs.build.outputs.release_tag }}
DOCS_RELEASE_AUDIT_EVIDENCE: docs-release-audit-evidence.json
DOCS_RELEASE_AUDIT_HANDOFF: docs-release-audit-handoff.json
DOCS_RELEASE_AUDIT_ENFORCEMENT: advisory
run: scripts/ci/check-docs-release-audit.sh
- name: Upload docs release audit evidence
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: docs-release-audit-evidence
path: |
docs-release-audit-evidence.json
docs-release-audit-handoff.json
if-no-files-found: warn
publish-test:
needs: build
runs-on: ubuntu-latest
if: >-
github.event_name == 'workflow_dispatch' &&
github.ref == 'refs/heads/main' &&
!inputs.dry_run && !inputs.publish
environment: test-pypi
permissions:
actions: read
contents: read
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
fetch-depth: 0
persist-credentials: false
ref: ${{ github.sha }}
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
artifact-ids: ${{ needs.build.outputs.artifact-id }}
digest-mismatch: error
github-token: ${{ github.token }}
path: isolated-python-dist
repository: ${{ github.repository }}
run-id: ${{ needs.build.outputs.source-run-id }}
- name: Validate the exact producer artifact before use
env:
ARTIFACT_DIRECTORY: isolated-python-dist
EXPECTED_ARTIFACT_DIGEST: ${{ needs.build.outputs.artifact-digest }}
EXPECTED_ARTIFACT_ID: ${{ needs.build.outputs.artifact-id }}
EXPECTED_SOURCE_RUN_ATTEMPT: ${{ needs.build.outputs.source-run-attempt }}
EXPECTED_SOURCE_RUN_ID: ${{ needs.build.outputs.source-run-id }}
run: |
set -euo pipefail
if [[ ! "$EXPECTED_ARTIFACT_DIGEST" =~ ^[0-9a-f]{64}$ ]]; then
printf 'producer artifact digest is not an exact SHA-256 digest\n' >&2
exit 1
fi
for identity in "$EXPECTED_ARTIFACT_ID" "$EXPECTED_SOURCE_RUN_ID" "$EXPECTED_SOURCE_RUN_ATTEMPT"; do
if [[ ! "$identity" =~ ^[1-9][0-9]*$ ]]; then
printf 'producer artifact identity is invalid\n' >&2
exit 1
fi
done
if [[ ! "$ARTIFACT_DIRECTORY" =~ ^isolated-[a-z0-9][a-z0-9._-]*$ ]]; then
printf 'artifact validation directory is unsafe\n' >&2
exit 1
fi
mapfile -d '' entries < <(
/usr/bin/find "$ARTIFACT_DIRECTORY" -mindepth 1 -maxdepth 1 -print0
)
if [ "${#entries[@]}" -ne 1 ] || [ ! -f "${entries[0]}" ] || [ -L "${entries[0]}" ]; then
printf 'artifact handoff must contain exactly one regular file\n' >&2
exit 1
fi
observed_digest="$(/usr/bin/sha256sum "${entries[0]}")"
observed_digest="${observed_digest%% *}"
if [ "$observed_digest" != "$EXPECTED_ARTIFACT_DIGEST" ]; then
printf 'artifact digest mismatch: expected %s, got %s\n' \
"$EXPECTED_ARTIFACT_DIGEST" "$observed_digest" >&2
exit 1
fi
printf 'validated artifact %s from run %s attempt %s\n' \
"$EXPECTED_ARTIFACT_ID" "$EXPECTED_SOURCE_RUN_ID" "$EXPECTED_SOURCE_RUN_ATTEMPT"
- name: Extract the validated TestPyPI handoff
run: |
mkdir dist
tar -xf isolated-python-dist/dist-handoff.tar -C dist
- name: Publish to TestPyPI
uses: pypa/gh-action-pypi-publish@ba38be9e461d3875417946c167d0b5f3d385a247 # release/v1
with:
password: ${{ secrets.TEST_PYPI_TOKEN }}
repository-url: https://test.pypi.org/legacy/
print-hash: true