Skip to content
Open
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
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,7 @@ tests/sentry/api/endpoints/test_organization_attribute_mappings.py @get
/src/sentry/api/urls.py @getsentry/owners-api
/api-docs/ @getsentry/owners-api
/tests/apidocs/ @getsentry/owners-api
/static/packages/api/ @getsentry/owners-api @getsentry/ocpo
/.github/workflows/openapi.yml @getsentry/owners-api
/.github/workflows/openapi-diff.yml @getsentry/owners-api
/src/sentry/conf/api_pagination_allowlist_do_not_modify.py @getsentry/owners-api
Expand Down
6 changes: 6 additions & 0 deletions .github/file-filters.yml
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,12 @@ api_docs: &api_docs
- 'api-docs/**'
- 'tests/apidocs/**'

# The @sentry/api package builds from the API spec, so it rebuilds whenever
# the spec might change, and when the package itself does.
api_client:
- *api_docs
- 'static/packages/api/**'

# This is the ultimate controller for backend.yml
backend_any_type: &backend_any_type
- *backend_all
Expand Down
49 changes: 48 additions & 1 deletion .github/workflows/backend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ jobs:
# Map a step output to a job output
outputs:
api_docs: ${{ steps.changes.outputs.api_docs }}
api_client: ${{ steps.changes.outputs.api_client }}
backend: ${{ steps.changes.outputs.backend_all_without_acceptance }}
backend_dependencies: ${{ steps.changes.outputs.backend_dependencies }}
backend_api_urls: ${{ steps.changes.outputs.backend_api_urls }}
Expand All @@ -50,7 +51,8 @@ jobs:
filters: .github/file-filters.yml

api-docs:
if: needs.files-changed.outputs.api_docs == 'true'
# api_client includes api_docs: the api-client job needs this job's spec.
if: needs.files-changed.outputs.api_client == 'true'
needs: files-changed
name: api docs test
runs-on: ${{ github.event_name == 'pull_request' && 'ubuntu-26.04-arm' || 'ubuntu-24.04' }}
Expand Down Expand Up @@ -83,6 +85,20 @@ jobs:
- name: Build and validate API docs
run: make validate-api-docs

- name: Build the RPC method schema
run: make build-rpc-schema

- name: Collect the @sentry/api spec files
run: pnpm --filter @sentry/api run build:spec

- name: Upload the @sentry/api spec files
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: api-spec
path: static/packages/api/spec/
if-no-files-found: error
retention-days: 1

- name: Wait for devservices
run: python3 .github/actions/setup-sentry/devservices-bg.py wait

Expand All @@ -96,6 +112,36 @@ jobs:
devservices logs
fi

# Builds, typechecks and tests the @sentry/api client against this commit's
# API spec, so a spec change that breaks the client cannot merge.
api-client:
if: needs.files-changed.outputs.api_client == 'true'
needs: [files-changed, api-docs]
name: api client
runs-on: ${{ github.event_name == 'pull_request' && 'ubuntu-26.04-arm' || 'ubuntu-24.04' }}
timeout-minutes: 20
env:
SENTRY_API_SPEC_DIR: ${{ github.workspace }}/.artifacts/api-spec
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7

- uses: ./.github/actions/setup-node-pnpm

- name: Download the @sentry/api spec files
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: api-spec
path: ${{ env.SENTRY_API_SPEC_DIR }}

- name: Build
run: pnpm --filter @sentry/api run build

- name: Typecheck
run: pnpm --filter @sentry/api run typecheck

- name: Test
run: pnpm --filter @sentry/api run test:vitest

# Selective testing - only on PRs, determine which tests to run based on coverage data.
# This job is skipped on push-to-master where the full suite runs instead.
select-tests:
Expand Down Expand Up @@ -761,6 +807,7 @@ jobs:
backend-required-check:
needs:
[
api-client,
api-docs,
backend-test,
backend-migration-tests,
Expand Down
103 changes: 91 additions & 12 deletions .github/workflows/openapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ jobs:
timeout-minutes: 90
permissions:
contents: read
outputs:
api_client: ${{ steps.changes.outputs.api_client }}
steps:
- name: github app token
id: app-token
Expand All @@ -38,10 +40,10 @@ jobs:
uses: ./.github/actions/setup-sentry
with:
mode: migrations
if: steps.changes.outputs.api_docs == 'true'
if: steps.changes.outputs.api_client == 'true'

- name: Checkout getsentry/sentry-api-schema
if: steps.changes.outputs.api_docs == 'true'
if: steps.changes.outputs.api_client == 'true'
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
ref: 'main'
Expand All @@ -54,28 +56,105 @@ jobs:
- uses: ./.github/actions/setup-node-pnpm

- name: Build OpenAPI Derefed JSON
if: steps.changes.outputs.api_docs == 'true'
if: steps.changes.outputs.api_client == 'true'
run: |
make build-api-docs

- name: Build RPC method schema
if: steps.changes.outputs.api_docs == 'true'
run: |
sentry rpcschema --loglevel=WARNING > rpc_method_schema.json
if: steps.changes.outputs.api_client == 'true'
run: make build-rpc-schema

- name: Collect the @sentry/api spec files
if: steps.changes.outputs.api_client == 'true'
run: pnpm --filter @sentry/api run build:spec

- name: Upload the @sentry/api spec files
if: steps.changes.outputs.api_client == 'true'
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: api-spec
path: static/packages/api/spec/
if-no-files-found: error
retention-days: 1

- name: Copy artifacts into getsentry/sentry-api-schema
if: steps.changes.outputs.api_docs == 'true'
run: |
cp tests/apidocs/openapi-derefed.json sentry-api-schema
cp src/sentry/apidocs/api_ownership_stats_dont_modify.json sentry-api-schema
cp rpc_method_schema.json sentry-api-schema
if: steps.changes.outputs.api_client == 'true'
run: cp static/packages/api/spec/* sentry-api-schema

- name: Git Commit & Push
uses: stefanzweifel/git-auto-commit-action@0049e3fa4059ca715255fbbcb7dea4516f02ce0a # v4.15.3
if: steps.changes.outputs.api_docs == 'true'
if: steps.changes.outputs.api_client == 'true'
with:
repository: sentry-api-schema
branch: main
commit_message: Generated
commit_user_email: 271575301+sentry-api-schema-updater[bot]@users.noreply.github.com
commit_user_name: sentry-api-schema-updater[bot]

# Releases @sentry/api (static/packages/api) when this commit changed what
# the package ships. The preflight script skips the run until a person has
# cut the first `api@` release with the Release workflow.
release-api-package:
needs: build_and_deref_json
# The spec files upload before the push to getsentry/sentry-api-schema, so
# a failed push does not hold up the release.
if: ${{ !cancelled() && needs.build_and_deref_json.outputs.api_client == 'true' }}
runs-on: ubuntu-24.04
timeout-minutes: 30
permissions:
contents: read
# One release at a time, and never cancel a running one: it may have
# pushed its release branch already. A newer run replaces the one waiting,
# so a burst of merges releases once.
concurrency:
group: api-package-release
cancel-in-progress: false
steps:
- name: Get auth token
id: token
uses: actions/create-github-app-token@5d869da34e18e7287c1daad50e0b8ea0f506ce69 # v1.11.0
with:
app-id: ${{ vars.SENTRY_RELEASE_BOT_CLIENT_ID }}
private-key: ${{ secrets.SENTRY_RELEASE_BOT_PRIVATE_KEY }}
owner: getsentry
repositories: |
sentry
publish

- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
token: ${{ steps.token.outputs.token }}
# Tags and history for the preflight and craft, without the blobs.
fetch-depth: 0
filter: blob:none

# Craft prepares from the current branch and expects the default one.
- name: Put master on this commit
run: git checkout -B master

- uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4
with:
node-version-file: '.node-version'

- name: Download the @sentry/api spec files
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: api-spec
path: ${{ runner.temp }}/api-spec

- name: Check what to release
id: preflight
env:
SPEC_DIR: ${{ runner.temp }}/api-spec
GH_TOKEN: ${{ steps.token.outputs.token }}
run: .github/workflows/scripts/api-package-release-preflight.sh

- name: Prepare release
if: steps.preflight.outputs.version != ''
uses: getsentry/craft@2e29b0819113d334c15da3d8cac2138cb9dc111a # 3.9.0
env:
GITHUB_TOKEN: ${{ steps.token.outputs.token }}
with:
workspace: static/packages/api
version: ${{ steps.preflight.outputs.version }}
publish_repo: getsentry/publish
25 changes: 24 additions & 1 deletion .github/workflows/package-pack.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,41 @@ jobs:
runs-on: ubuntu-24.04
env:
PACKAGE_DIR: static/packages/${{ needs.workspace.outputs.name }}
# @sentry/api builds from the API spec, which the Python app generates.
BUILDS_FROM_API_SPEC: ${{ needs.workspace.outputs.name == 'api' }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup sentry python env
if: env.BUILDS_FROM_API_SPEC == 'true'
uses: ./.github/actions/setup-sentry
with:
skip-devservices: 'true'
- uses: ./.github/actions/setup-node-pnpm
- run: pnpm install --frozen-lockfile --ignore-scripts
- name: Configure sentry
if: env.BUILDS_FROM_API_SPEC == 'true'
run: sentry init
- name: Build the API spec
if: env.BUILDS_FROM_API_SPEC == 'true'
run: make build-api-docs build-rpc-schema
- name: Build the package
if: env.BUILDS_FROM_API_SPEC == 'true'
working-directory: ${{ env.PACKAGE_DIR }}
run: pnpm run build
- name: Pack release artifact
working-directory: ${{ env.PACKAGE_DIR }}
run: pnpm pack --pack-destination .artifacts
# Craft's github target attaches every file in the artifact to the
# release; its npm target publishes only the tarball.
- name: Add the API spec files to the release artifact
if: env.BUILDS_FROM_API_SPEC == 'true'
working-directory: ${{ env.PACKAGE_DIR }}
run: cp spec/* .artifacts
- name: Upload release artifact
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
# Craft fetches `{{workspaceName}}-tarball` from this workflow.
name: ${{ needs.workspace.outputs.name }}-tarball
path: ${{ env.PACKAGE_DIR }}/.artifacts/*.tgz
path: ${{ env.PACKAGE_DIR }}/.artifacts/*
include-hidden-files: true
if-no-files-found: error
1 change: 1 addition & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ on:
default: sentry
options:
- sentry
- static/packages/api
- static/packages/scraps
- static/packages/icons
- static/packages/search-syntax
Expand Down
104 changes: 104 additions & 0 deletions .github/workflows/scripts/api-package-release-preflight.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#!/bin/sh
# Decides whether openapi.yml releases @sentry/api from HEAD, a master commit
# whose spec files are in SPEC_DIR. To release, writes the version bump for
# craft to $GITHUB_OUTPUT as version=<bump>. To skip, writes nothing.
#
# The release-branch and tag checks follow getsentry/sentry-api-schema#137's
# release-preflight.sh, so a burst of merges releases once and nothing that
# lands while a release is pending is left unreleased.
#
# Environment:
# SPEC_DIR The package's spec/ files built from HEAD.
# GH_TOKEN A token that can read this repository's releases.
# GITHUB_REPOSITORY Set by GitHub Actions.
# GITHUB_OUTPUT Set by GitHub Actions.
set -eu

package_dir=static/packages/api
tag_prefix=api@

skip() {
printf '::notice::%s\n' "$1"
exit 0
}

if [ ! -f "$SPEC_DIR/openapi-derefed.json" ]; then
printf '::error::%s has no openapi-derefed.json.\n' "$SPEC_DIR"
exit 1
fi

# Automatic releases start after a person cuts the first one from here.
if ! base=$(git describe --tags --abbrev=0 --match "$tag_prefix*" 2>/dev/null); then
skip "No $tag_prefix release yet. Cut the first one with the Release workflow."
fi

# Craft refuses to cut a release while one is pending. When that release
# merges into master, the run for the merge releases whatever landed meanwhile.
pending=$(git ls-remote --heads origin "refs/heads/${tag_prefix%@}/release/*")
if [ -n "$pending" ]; then
skip "A release is still pending: $pending"
fi

# Craft takes the next version from the latest tag in HEAD's history. A newer
# tag outside it means HEAD predates that release, and the run for that
# release's merge covers this commit.
tags=$(git tag --list "$tag_prefix*" --sort=-version:refname)
newest=${tags%%[[:space:]]*}
if [ "$newest" != "$base" ]; then
skip "HEAD predates release $newest."
fi

released=$(mktemp -d)
trap 'rm -rf "$released"' EXIT
trap 'exit 1' INT TERM
# The last release's GitHub release has the spec files it was built from as
# assets. Craft's github target attaches them before craft merges the release
# branch, so a run for that merge sees them. A release without them cannot be
# compared, and treating every file as changed would cut a release for nothing.
json_assets=$(timeout 60 gh release view "$base" --repo "$GITHUB_REPOSITORY" \
--json assets --jq '[.assets[].name | select(endswith(".json"))] | length')
if [ "$json_assets" -eq 0 ]; then
printf '::error::Release %s has no spec files attached to compare with.\n' "$base"
exit 1
fi
timeout 300 gh release download "$base" --repo "$GITHUB_REPOSITORY" \
--pattern '*.json' --dir "$released"

spec_changed=false
data_changed=false
for file in "$SPEC_DIR"/*; do
name=${file##*/}
# cmp exits 1 when the files differ and 2 when the release lacks the file.
if ! cmp -s "$file" "$released/$name"; then
if [ "$name" = openapi-derefed.json ]; then
spec_changed=true
else
data_changed=true
fi
fi
Comment thread
cursor[bot] marked this conversation as resolved.
Resolved
done

if git diff --quiet "$base" HEAD -- "$package_dir"; then
package_changed=false
else
status=$?
if [ "$status" -ne 1 ]; then
exit "$status"
fi
package_changed=true
fi

# Spec updates are minor releases, as they were in sentry-api-schema. Package
# changes take craft's bump from their commits. Ownership or RPC schema
# changes alone are patches.
if [ "$spec_changed" = true ]; then
bump='minor'
elif [ "$package_changed" = true ]; then
bump='auto'
elif [ "$data_changed" = true ]; then
bump='patch'
else
skip "Nothing changed since $base."
fi
printf '::notice::Releasing a %s bump on %s.\n' "$bump" "$base"
printf 'version=%s\n' "$bump" >>"$GITHUB_OUTPUT"
Loading
Loading