diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..dd0158c --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,56 @@ +name: release + +# Publish to PyPI when a GitHub Release is published (the GitHub release flow). +# Uses PyPI Trusted Publishing (OIDC) — no API token is stored in the repo. +# One-time PyPI setup: add a trusted publisher for project `optimum-keysync` +# with this repo, workflow file `release.yml`, and environment `pypi`. +on: + release: + types: [published] + +permissions: + contents: read + +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false + +jobs: + pypi: + runs-on: ubuntu-latest + timeout-minutes: 10 + environment: + name: pypi + url: https://pypi.org/project/optimum-keysync/ + permissions: + contents: read + id-token: write # OIDC token for Trusted Publishing; no stored secret + steps: + # Action versions pinned to commit SHAs (tag in the comment) per the + # OpenSSF / GitHub-hardening guidance used across this repo's workflows. + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 + with: + python-version: "3.12" + + # Guard against tag/version drift: the package version (single source, + # src/optimum_keysync/__init__.py) must match the release tag (with an + # optional leading `v`), so a release never publishes a different version + # than it claims. + - name: verify tag matches package version + run: | + pkg=$(PYTHONPATH=src python -c "import optimum_keysync; print(optimum_keysync.__version__)") + tag="${GITHUB_REF_NAME#v}" + if [ "$pkg" != "$tag" ]; then + echo "::error::release tag ${GITHUB_REF_NAME} (-> ${tag}) does not match pyproject version ${pkg}" + exit 1 + fi + + - name: build sdist + wheel + run: | + python -m pip install --upgrade build + python -m build + + - name: publish to PyPI + uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0 diff --git a/README.md b/README.md index 003d08a..3ed6746 100644 --- a/README.md +++ b/README.md @@ -130,3 +130,16 @@ pytest Tests are hermetic: `respx` stubs every outbound HTTP call, so no live API or beacon access is required. + +## Releasing + +Publishing to PyPI is automated via `.github/workflows/release.yml`, which runs +when a GitHub Release is published and uploads with PyPI Trusted Publishing +(OIDC) — no token is stored in the repo. To cut a release: + +1. Bump `version` in `pyproject.toml` and merge to `main`. +2. Publish a GitHub Release tagged `v` (e.g. `v1.0.0`). + +The workflow verifies the tag matches the package version, builds the sdist and +wheel, and publishes. One-time setup: register the trusted publisher on PyPI +(project `optimum-keysync`, workflow `release.yml`, environment `pypi`). diff --git a/examples/kubernetes/cronjob.yaml b/examples/kubernetes/cronjob.yaml index 9f3f3e1..c58ab59 100644 --- a/examples/kubernetes/cronjob.yaml +++ b/examples/kubernetes/cronjob.yaml @@ -19,7 +19,7 @@ spec: restartPolicy: Never containers: - name: keysync - image: ghcr.io/getoptimum/optimum-keysync:0.1.0 + image: ghcr.io/getoptimum/optimum-keysync:1.0.0 args: ["sync", "--apply", "--log-format", "json"] envFrom: - configMapRef: diff --git a/pyproject.toml b/pyproject.toml index 7f36733..142b0db 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,8 @@ build-backend = "setuptools.build_meta" [project] name = "optimum-keysync" -version = "0.1.0" +# Single source of truth: src/optimum_keysync/__init__.py:__version__ +dynamic = ["version"] description = "Reconcile customer-owned Ethereum validator indices against Optimum's validator registry (console API). Cron-friendly CLI that authenticates with an ovi_live_* operator API key, diffs the desired set against the registry, and applies the delta." readme = "README.md" requires-python = ">=3.11" @@ -12,7 +13,7 @@ license = { text = "MIT" } authors = [{ name = "Optimum" }] keywords = ["ethereum", "validator", "optimum", "keysync", "sync", "operator"] classifiers = [ - "Development Status :: 3 - Alpha", + "Development Status :: 5 - Production/Stable", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", @@ -42,6 +43,9 @@ keysync = "optimum_keysync.cli:main" Homepage = "https://github.com/getoptimum/optimum-customer-key-import" Issues = "https://github.com/getoptimum/optimum-customer-key-import/issues" +[tool.setuptools.dynamic] +version = { attr = "optimum_keysync.__version__" } + [tool.setuptools.packages.find] where = ["src"] diff --git a/src/optimum_keysync/__init__.py b/src/optimum_keysync/__init__.py index 25c6a08..fd86912 100644 --- a/src/optimum_keysync/__init__.py +++ b/src/optimum_keysync/__init__.py @@ -1,5 +1,5 @@ """optimum-keysync: reconcile operator validator assignments against the Optimum console API.""" -__version__ = "0.1.0" +__version__ = "1.0.0" __all__ = ["__version__"]