Skip to content

ci: add publish-from-tag workflow for PyPI#2758

Merged
paulteehan merged 2 commits into
mainfrom
feat/pypi-publish-from-tag
Jun 16, 2026
Merged

ci: add publish-from-tag workflow for PyPI#2758
paulteehan merged 2 commits into
mainfrom
feat/pypi-publish-from-tag

Conversation

@paulteehan

Copy link
Copy Markdown
Contributor

Why

Releases are now driven by release-buddy (bump → PR → merge → tag → GitHub Release). But the only workflow that publishes to public PyPI is release.yaml ("one-button release"), which is workflow_dispatch-only and refuses to run against an already-existing tag (its validate job hard-fails on Check tag does not exist). Since the tag already exists by the time we'd publish, release.yaml can't be used.

Result: nothing reached public pypi.org between v4.7.0 (2026-04-17) and v4.14.0 — only dev PyPI kept flowing. Internal consumers pull core from private/enterprise PyPI so it went unnoticed; only OSS users were affected (stuck on 4.7.0). Affects all 13 release-matrix packages (soda-core + soda-postgres, soda-bigquery, …).

What

New .github/workflows/publish.yaml that only builds a given ref and uploads — it never tags, bumps, or creates releases.

  • release: published → auto-publishes new releases. This is the permanent fix: release-buddy creates the Release, this publishes it.
  • workflow_dispatch (input: tag, dry_run) → backfill/recovery for a specific existing tag (e.g. v4.14.0, and the 4.8.0–4.13.0 backlog).
  • Uploads to both Soda PyPI and public PyPI, reusing the exact secret wiring from release.yaml (PYPI_API_TOKEN, AWS-secrets Soda creds, production-release environment).
  • twine upload --skip-existing → idempotent; safe re-runs and tolerates a registry already having the version.
  • Verifies built artifacts are the clean tag version (not a .devN) before any upload — PyPI uploads are irreversible.

Immediate use

After merge: Actions → Publish to PyPI → Run workflow → tag v4.14.0 (run once with dry_run: true first to confirm builds). This backfills the current release for OSS users.

Follow-ups (not in this PR)

  • Backfill v4.8.0–v4.13.0 on demand (dispatch per tag).
  • release.yaml's inline build-and-publish becomes redundant once release: published is live — consider trimming it to avoid double work (--skip-existing already makes the overlap harmless).
  • Add a post-release public-PyPI verification gate in release-buddy (the existing checkReleaseArtifact() already hits pypi.org) so a stalled publish can never go unnoticed again.

🤖 Generated with Claude Code

Adds .github/workflows/publish.yaml: builds an already-tagged release and
uploads to Soda PyPI + public PyPI. Unlike release.yaml it never tags,
bumps, or creates releases.

Closes the gap where releases driven by release-buddy (which tags + creates
the GitHub Release itself) never reached public PyPI: release.yaml refuses
to run against an existing tag, so nothing published publicly between v4.7.0
(2026-04-17) and v4.14.0 — only dev PyPI kept flowing.

- release: published  -> auto-publishes new releases (the permanent fix)
- workflow_dispatch    -> backfill/recovery for a specific tag
- twine --skip-existing -> idempotent; safe re-runs, tolerates a registry
  already having the version
- verifies built artifacts are the clean tag version (not .devN) before upload

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# twine runs with --skip-existing, so re-runs are safe and the automatic path
# does not fail when a registry already has the version (e.g. Soda PyPI).

on:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't this run by default on tags?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes good catch, fixed

A GitHub Release is best-effort (release-buddy tolerates `gh release create`
failing), so `release: published` could silently skip a publish — the very
gap this workflow closes. The release tag is always pushed, so key off
`push: tags: [v*]` instead. workflow_dispatch is retained for backfill.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@m1n0 m1n0 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for addressing this!

@paulteehan paulteehan merged commit 1cd3557 into main Jun 16, 2026
22 checks passed
@paulteehan paulteehan deleted the feat/pypi-publish-from-tag branch June 16, 2026 14:15
paulteehan added a commit that referenced this pull request Jun 16, 2026
Reverses the "drop Soda PyPI" half of this branch — soda-core clean
releases are still needed on pypi.cloud.soda.io. The original failure was
not that the registry is wrong, but that #2758 passed --skip-existing to
every leg, and pypi.cloud.soda.io (devpi) rejects that flag:

  UnsupportedConfiguration: 'https://pypi.cloud.soda.io' does not have
  support for the following features: --skip-existing

Fix per-registry instead of removing the leg:
  - public PyPI        -> keeps `twine upload --skip-existing` (supported)
  - pypi.cloud.soda.io -> upload plain; capture output and treat an
                          "already exists" / 409 rejection as success, so
                          re-runs and backfills stay idempotent

Keeps this branch's single-approval gate (one approval email per run, not
one per matrix leg). Restores the AWS secrets fetch the Soda leg needs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
paulteehan added a commit that referenced this pull request Jun 16, 2026
* ci(publish): public PyPI only + single approval gate

Two fixes to publish.yaml from the first 4.14.0 run:

1. Drop the Soda PyPI (pypi.cloud.soda.io) upload. It errored on
   `--skip-existing` (unsupported by that index), and it shouldn't be a
   target: that index serves the soda-library 1.x line that scan-launcher
   builds from, and soda-library's connectors share package names with
   soda-core's (soda-postgres, soda-snowflake, ...). Publishing soda-core's
   4.x there collides with soda-library's identically-named 1.x packages.
   Nothing consumes soda-core clean releases from cloud (launchers pull
   soda-core dev builds from the dev platform index). soda-core clean
   releases belong on public PyPI only. Removes the now-unused AWS
   secrets-manager fetch too.

2. Collapse the 14 environment approvals into one. The matrix publish job
   referenced `production-release` directly, so each of the 14 legs raised
   its own approval (14 emails). A tiny `approve` gate job now carries the
   environment; the matrix depends on it and drops the environment. Safe
   because PYPI_API_TOKEN is a repo-level secret.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* ci(publish): restore Soda PyPI upload, fix --skip-existing on devpi

Reverses the "drop Soda PyPI" half of this branch — soda-core clean
releases are still needed on pypi.cloud.soda.io. The original failure was
not that the registry is wrong, but that #2758 passed --skip-existing to
every leg, and pypi.cloud.soda.io (devpi) rejects that flag:

  UnsupportedConfiguration: 'https://pypi.cloud.soda.io' does not have
  support for the following features: --skip-existing

Fix per-registry instead of removing the leg:
  - public PyPI        -> keeps `twine upload --skip-existing` (supported)
  - pypi.cloud.soda.io -> upload plain; capture output and treat an
                          "already exists" / 409 rejection as success, so
                          re-runs and backfills stay idempotent

Keeps this branch's single-approval gate (one approval email per run, not
one per matrix leg). Restores the AWS secrets fetch the Soda leg needs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants