Repository navigation
fix(sdk-python): report and flag output lost to a mapping failure (#186) #341
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release | |
| on: | |
| push: | |
| branches: [main] | |
| workflow_dispatch: | |
| inputs: | |
| release_tag: | |
| description: Published release tag to retry from main | |
| required: true | |
| type: string | |
| concurrency: | |
| group: release-${{ inputs.release_tag || github.sha }} | |
| cancel-in-progress: false | |
| permissions: {} | |
| jobs: | |
| release-please: | |
| if: github.event_name == 'push' | |
| concurrency: | |
| group: release-please-${{ github.ref }} | |
| cancel-in-progress: false | |
| # The default queue cancels a waiting release-please job when another push arrives, leaving that merge unpublished. | |
| queue: max | |
| runs-on: ubuntu-24.04 | |
| permissions: | |
| contents: write | |
| issues: write | |
| pull-requests: write | |
| outputs: | |
| paths: ${{ steps.release.outputs.paths_released }} | |
| steps: | |
| - env: | |
| RELEASE_PLEASE_TOKEN: ${{ secrets.RELEASE_PLEASE_TOKEN }} | |
| run: | | |
| if [[ -z "$RELEASE_PLEASE_TOKEN" ]]; then | |
| echo "RELEASE_PLEASE_TOKEN is required" >&2 | |
| exit 1 | |
| fi | |
| - id: release | |
| uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 | |
| with: | |
| config-file: release-please-config.json | |
| manifest-file: .release-please-manifest.json | |
| token: ${{ secrets.RELEASE_PLEASE_TOKEN }} | |
| discover-release: | |
| needs: release-please | |
| if: >- | |
| !cancelled() && (github.event_name == 'workflow_dispatch' || | |
| needs.release-please.result == 'success') | |
| runs-on: ubuntu-24.04 | |
| permissions: | |
| contents: read | |
| outputs: | |
| npm-packages: ${{ steps.packages.outputs.npm }} | |
| python-packages: ${{ steps.packages.outputs.python }} | |
| source-sha: ${{ steps.packages.outputs.sha }} | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| ref: ${{ github.sha }} | |
| - id: packages | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| RELEASE_TAG: ${{ inputs.release_tag }} | |
| RELEASED_PATHS: ${{ needs.release-please.outputs.paths || '[]' }} | |
| run: | | |
| npm=() | |
| python=() | |
| tags=() | |
| matches=0 | |
| released_matches=0 | |
| while IFS= read -r path; do | |
| component=$(jq -er --arg path "$path" '.packages[$path].component' release-please-config.json) | |
| release_type=$(jq -er --arg path "$path" '.packages[$path]["release-type"]' release-please-config.json) | |
| version=$(jq -r --arg path "$path" '.[$path] // empty' .release-please-manifest.json) | |
| # A package added since the last release has no version until its first release. | |
| [[ -n "$version" ]] || continue | |
| tag="$component-v$version" | |
| if [[ "$GITHUB_EVENT_NAME" == "workflow_dispatch" ]]; then | |
| [[ "$tag" == "$RELEASE_TAG" ]] || continue | |
| elif jq -e --arg path "$path" 'any(.[]; . == $path)' <<< "$RELEASED_PATHS" > /dev/null; then | |
| released_matches=$((released_matches + 1)) | |
| # A rerun after release-please failed partway omits releases it already created, so their tags select them. | |
| elif [[ "$(git rev-parse -q --verify "refs/tags/$tag^{commit}" || true)" != "$GITHUB_SHA" ]]; then | |
| continue | |
| fi | |
| case "$release_type" in | |
| node) npm+=("$path") ;; | |
| python) python+=("$path") ;; | |
| # The experiment action has no package: the tag-experiment-action job moves its major tag. | |
| simple) continue ;; | |
| *) | |
| echo "Unsupported release type for $path: $release_type" >&2 | |
| exit 1 | |
| ;; | |
| esac | |
| tags+=("$tag") | |
| matches=$((matches + 1)) | |
| done < <(jq -r '.packages | to_entries[] | .key' release-please-config.json) | |
| if [[ "$GITHUB_EVENT_NAME" == "workflow_dispatch" && "$matches" != 1 ]]; then | |
| echo "Expected one package for $RELEASE_TAG, found $matches" >&2 | |
| exit 1 | |
| fi | |
| released=$(jq -er 'length' <<< "$RELEASED_PATHS") | |
| if [[ "$GITHUB_EVENT_NAME" != "workflow_dispatch" && "$released_matches" != "$released" ]]; then | |
| echo "Expected $released configured packages for released paths, found $released_matches" >&2 | |
| exit 1 | |
| fi | |
| for tag in "${tags[@]}"; do | |
| RELEASE_TAG="$tag" .github/scripts/release-source.sh > /dev/null | |
| draft=$(gh release view --repo "$GITHUB_REPOSITORY" --json isDraft --jq '.isDraft' -- "$tag") | |
| if [[ "$draft" != "false" ]]; then | |
| echo "Release $tag is still a draft" >&2 | |
| exit 1 | |
| fi | |
| done | |
| npm_json=$(printf '%s\n' "${npm[@]}" | jq --raw-input --slurp --compact-output 'split("\n") | map(select(length > 0))') | |
| python_json=$(printf '%s\n' "${python[@]}" | jq --raw-input --slurp --compact-output 'split("\n") | map(select(length > 0))') | |
| { | |
| echo "npm=$npm_json" | |
| echo "python=$python_json" | |
| echo "sha=$GITHUB_SHA" | |
| } >> "$GITHUB_OUTPUT" | |
| build-npm: | |
| needs: discover-release | |
| if: >- | |
| !cancelled() && needs.discover-release.result == 'success' && | |
| needs.discover-release.outputs.npm-packages != '[]' | |
| runs-on: ubuntu-24.04 | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: pnpm/action-setup@ff378ebe6b225b0680b81c1ad4498ae0d1d3a5e3 | |
| with: | |
| version: 11.25.0 | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 | |
| with: | |
| persist-credentials: false | |
| ref: ${{ needs.discover-release.outputs.source-sha }} | |
| - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 | |
| with: | |
| node-version: 24.19.0 | |
| package-manager-cache: false | |
| - run: pnpm install --frozen-lockfile | |
| - env: | |
| PACKAGES: ${{ needs.discover-release.outputs.npm-packages }} | |
| run: | | |
| output="$RUNNER_TEMP/npm" | |
| mkdir -p "$output" | |
| while IFS= read -r package; do | |
| pnpm --dir "$package" run build | |
| metadata=$(pnpm --dir "$package" pack --pack-destination "$output" --json) | |
| tarball=$(jq -r '.filename' <<< "$metadata") | |
| .github/scripts/release-artifact.sh "$package" "$tarball" | |
| basename "$tarball" >> "$output/publish-order.txt" | |
| done < <(jq -r '.[]' <<< "$PACKAGES") | |
| - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a | |
| with: | |
| name: npm-packages | |
| path: ${{ runner.temp }}/npm | |
| retention-days: 30 | |
| if-no-files-found: error | |
| publish-npm: | |
| needs: [discover-release, build-npm] | |
| if: "!cancelled() && needs.build-npm.result == 'success'" | |
| runs-on: ubuntu-24.04 | |
| permissions: | |
| id-token: write | |
| steps: | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c | |
| with: | |
| name: npm-packages | |
| path: npm | |
| - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 | |
| with: | |
| node-version: 24.19.0 | |
| package-manager-cache: false | |
| registry-url: https://registry.npmjs.org | |
| - run: npm install --global npm@12.0.2 | |
| - env: | |
| NPM_CONFIG_PROVENANCE: ${{ github.event.repository.private && 'false' || 'true' }} | |
| run: | | |
| while IFS= read -r tarball; do | |
| metadata=$(tar -xOf "npm/$tarball" package/package.json) | |
| name=$(jq -r '.name' <<< "$metadata") | |
| version=$(jq -r '.version' <<< "$metadata") | |
| remote_integrity=$(npm view "$name@$version" dist.integrity 2>/dev/null || true) | |
| if [[ -n "$remote_integrity" ]]; then | |
| local_integrity="sha512-$(openssl dgst -sha512 -binary "npm/$tarball" | openssl base64 -A)" | |
| if [[ "$remote_integrity" != "$local_integrity" ]]; then | |
| echo "Integrity mismatch for $name@$version" >&2 | |
| exit 1 | |
| fi | |
| echo "$name@$version is already published with matching integrity" | |
| continue | |
| fi | |
| while IFS=$'\t' read -r dependency range; do | |
| for attempt in {1..60}; do | |
| dependency_error=$(npm view "${dependency}@${range}" version --json 2>&1) && continue 2 | |
| [[ "$attempt" == 60 ]] || sleep 10 | |
| done | |
| echo "Cannot publish $name@$version: unable to verify that published $dependency satisfies $range" >&2 | |
| printf '%s\n' "$dependency_error" >&2 | |
| exit 1 | |
| done < <(jq -r ' | |
| ((.dependencies // {}), (.peerDependencies // {})) | to_entries[] | | |
| select(.key | startswith("@telemetry-dev/")) | | |
| [.key, .value] | @tsv | |
| ' <<< "$metadata") | |
| npm publish "./npm/$tarball" --access public | |
| done < npm/publish-order.txt | |
| build-python: | |
| needs: discover-release | |
| if: >- | |
| !cancelled() && needs.discover-release.result == 'success' && | |
| needs.discover-release.outputs.python-packages != '[]' | |
| runs-on: ubuntu-24.04 | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 | |
| with: | |
| persist-credentials: false | |
| ref: ${{ needs.discover-release.outputs.source-sha }} | |
| - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 | |
| with: | |
| version: 0.12.5 | |
| enable-cache: false | |
| - env: | |
| PACKAGES: ${{ needs.discover-release.outputs.python-packages }} | |
| run: | | |
| output="$RUNNER_TEMP/python" | |
| mkdir -p "$output" | |
| while IFS= read -r package; do | |
| name=${package#sdks/} | |
| uv build --no-sources "$package" --out-dir "$output/$name" | |
| for artifact in "$output/$name"/*; do | |
| .github/scripts/release-artifact.sh "$package" "$artifact" | |
| done | |
| echo "$name" >> "$output/publish-order.txt" | |
| done < <(jq -r '.[]' <<< "$PACKAGES") | |
| - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a | |
| with: | |
| name: python-packages | |
| path: ${{ runner.temp }}/python | |
| retention-days: 30 | |
| if-no-files-found: error | |
| publish-python: | |
| needs: [discover-release, build-python] | |
| if: "!cancelled() && needs.build-python.result == 'success'" | |
| runs-on: ubuntu-24.04 | |
| environment: pypi | |
| permissions: | |
| contents: read | |
| id-token: write | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 | |
| with: | |
| persist-credentials: false | |
| ref: ${{ needs.discover-release.outputs.source-sha }} | |
| sparse-checkout: .github/scripts | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c | |
| with: | |
| name: python-packages | |
| path: python | |
| - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 | |
| with: | |
| version: 0.12.5 | |
| enable-cache: false | |
| - env: | |
| RELEASE_DEPENDENCY_ATTEMPTS: 60 | |
| run: | | |
| while IFS= read -r package; do | |
| wheel=$(find "python/$package" -maxdepth 1 -type f -name '*.whl' -print -quit) | |
| .github/scripts/verify-python-release-dependencies.sh "$wheel" | |
| uv publish --trusted-publishing always --check-url https://pypi.org/simple "python/$package"/* | |
| done < python/publish-order.txt | |
| tag-experiment-action: | |
| needs: release-please | |
| concurrency: | |
| group: tag-experiment-action | |
| cancel-in-progress: false | |
| # The default queue cancels a waiting job when another push arrives, and that release then keeps the old major tag. | |
| queue: max | |
| runs-on: ubuntu-24.04 | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| ref: ${{ github.sha }} | |
| - env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| version=$(jq -r '.["actions/experiment"] // empty' .release-please-manifest.json) | |
| # A rerun after release-please failed partway gets no outputs for the release, so its tag selects it. | |
| if [[ -z "$version" || "$(git rev-parse -q --verify "refs/tags/experiment-action-v$version^{commit}" || true)" != "$GITHUB_SHA" ]]; then | |
| exit 0 | |
| fi | |
| # Workflows use the major tag, which moves to each release of its major version, but never back. | |
| major="experiment-action-v${version%%.*}" | |
| current=$(git rev-parse -q --verify "refs/tags/$major^{commit}" || true) | |
| if [[ -z "$current" ]]; then | |
| gh api --method POST "repos/$GITHUB_REPOSITORY/git/refs" -f "ref=refs/tags/$major" -f "sha=$GITHUB_SHA" | |
| elif git merge-base --is-ancestor "$current" "$GITHUB_SHA"; then | |
| gh api --method PATCH "repos/$GITHUB_REPOSITORY/git/refs/tags/$major" -f "sha=$GITHUB_SHA" -F force=true | |
| else | |
| echo "$major is at a release after experiment-action-v$version" | |
| fi |