Skip to content

fix(sdk-python): report and flag output lost to a mapping failure (#186) #341

fix(sdk-python): report and flag output lost to a mapping failure (#186)

fix(sdk-python): report and flag output lost to a mapping failure (#186) #341

Workflow file for this run

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