From 3e0875d482664bc901cc93802deb8b0018607c12 Mon Sep 17 00:00:00 2001 From: kramaranya Date: Thu, 21 Aug 2025 20:29:55 +0100 Subject: [PATCH 01/36] feat(ci): Add release CI job Signed-off-by: kramaranya --- .github/workflows/release.yml | 117 ++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..e69501ab --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,117 @@ +name: Release + +on: + push: + tags: + - 'v*.*.*' + - 'v*.*.*-rc.*' + - 'v*.*.*-alpha.*' + - 'v*.*.*-beta.*' + +permissions: + contents: write + id-token: write + +jobs: + test: + name: Run Tests + uses: ./.github/workflows/test-python.yaml + + build: + name: Build Package + needs: [test] + runs-on: ubuntu-latest + outputs: + version: ${{ steps.version.outputs.version }} + tag: ${{ steps.version.outputs.tag }} + is-prerelease: ${{ steps.version.outputs.is-prerelease }} + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Setup build environment + run: | + make verify + + - name: Extract version info + id: version + run: | + VERSION=${GITHUB_REF#refs/tags/v} + TAG=${GITHUB_REF#refs/tags/} + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "tag=$TAG" >> $GITHUB_OUTPUT + + if [[ "$VERSION" =~ -rc\.|alpha|beta ]]; then + echo "is-prerelease=true" >> $GITHUB_OUTPUT + else + echo "is-prerelease=false" >> $GITHUB_OUTPUT + fi + + - name: Auto-update version from git tag + run: | + TAG_VERSION="${{ steps.version.outputs.version }}" + echo "Updating version to $TAG_VERSION" + + sed -i 's/__version__ = "[^"]*"/__version__ = "'$TAG_VERSION'"/' kubeflow/__init__.py + + echo "Version automatically updated to $TAG_VERSION" + + - name: Auto-update version files only + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + + if git diff --quiet kubeflow/__init__.py; then + echo "No version changes to commit" + else + echo "Committing version updates" + git add kubeflow/__init__.py + git commit -m "chore: update version to ${{ steps.version.outputs.tag }}" || true + + if [[ "${GITHUB_REF}" =~ refs/heads/release-.* ]]; then + echo "Pushing version updates back to release branch" + git push origin HEAD:${GITHUB_REF#refs/heads/} + fi + fi + + - name: Build package + run: | + uv build + + - name: Validate package + run: | + uvx twine check dist/* + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: dist-${{ steps.version.outputs.version }} + path: dist/ + + publish-pypi: + name: Publish to PyPI + needs: [build] + runs-on: ubuntu-latest + environment: + name: release + url: https://pypi.org/project/kubeflow-sdk/ + + steps: + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: dist-${{ needs.build.outputs.version }} + path: dist/ + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + verbose: true From 309db19fe76cb0ec96e4dd6e9b24911524a79ed1 Mon Sep 17 00:00:00 2001 From: kramaranya Date: Thu, 21 Aug 2025 20:31:38 +0100 Subject: [PATCH 02/36] feat(ci): Add GitHub Release to CI Signed-off-by: kramaranya --- .github/workflows/release.yml | 39 +++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e69501ab..75b36822 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -115,3 +115,42 @@ jobs: uses: pypa/gh-action-pypi-publish@release/v1 with: verbose: true + + github-release: + name: Create GitHub Release + needs: [build, publish-pypi] + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Extract changelog + id: changelog + run: | + VERSION="${{ needs.build.outputs.version }}" + + if [[ -f CHANGELOG.md ]]; then + CHANGELOG=$(awk "/^# \[?v?$VERSION\]?/,/^# \[?v?[0-9]/" CHANGELOG.md | sed '$d' | tail -n +2) + if [[ -n "$CHANGELOG" ]]; then + echo "changelog<> $GITHUB_OUTPUT + echo "$CHANGELOG" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + else + echo "**Note:** CHANGELOG.md was not updated for this release" >> $GITHUB_OUTPUT + fi + else + echo "**Note:** CHANGELOG.md not found" >> $GITHUB_OUTPUT + fi + + - name: Create GitHub Release + uses: softprops/action-gh-release@v1 + with: + tag_name: ${{ needs.build.outputs.tag }} + name: Kubeflow SDK ${{ needs.build.outputs.tag }} + body: | + ${{ steps.changelog.outputs.changelog }} + draft: false + prerelease: ${{ needs.build.outputs.is-prerelease == 'true' }} + generate_release_notes: true From 03f3078f09beeaf64b52802ea5cb581663cc4bbb Mon Sep 17 00:00:00 2001 From: kramaranya Date: Thu, 21 Aug 2025 20:43:36 +0100 Subject: [PATCH 03/36] Change kubeflow sdk PyPI package name Signed-off-by: kramaranya --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 75b36822..cfcc6675 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -102,7 +102,7 @@ jobs: runs-on: ubuntu-latest environment: name: release - url: https://pypi.org/project/kubeflow-sdk/ + url: https://pypi.org/project/kubeflow/ steps: - name: Download build artifacts From 8cad32dee976deb2cb017123f2876ac8bb150890 Mon Sep 17 00:00:00 2001 From: kramaranya Date: Sat, 23 Aug 2025 00:09:30 +0100 Subject: [PATCH 04/36] Add kubeflow sdk version verification to CI Signed-off-by: kramaranya --- .github/workflows/release.yml | 30 +++++++++--------------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cfcc6675..b5dc3ae3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -55,33 +55,21 @@ jobs: echo "is-prerelease=false" >> $GITHUB_OUTPUT fi - - name: Auto-update version from git tag + - name: Verify version run: | TAG_VERSION="${{ steps.version.outputs.version }}" - echo "Updating version to $TAG_VERSION" + CODE_VERSION=$(python -c "import kubeflow; print(kubeflow.__version__)") - sed -i 's/__version__ = "[^"]*"/__version__ = "'$TAG_VERSION'"/' kubeflow/__init__.py + echo "Tag version: $TAG_VERSION" + echo "Code version: $CODE_VERSION" - echo "Version automatically updated to $TAG_VERSION" - - - name: Auto-update version files only - run: | - git config --local user.email "action@github.com" - git config --local user.name "GitHub Action" - - if git diff --quiet kubeflow/__init__.py; then - echo "No version changes to commit" - else - echo "Committing version updates" - git add kubeflow/__init__.py - git commit -m "chore: update version to ${{ steps.version.outputs.tag }}" || true - - if [[ "${GITHUB_REF}" =~ refs/heads/release-.* ]]; then - echo "Pushing version updates back to release branch" - git push origin HEAD:${GITHUB_REF#refs/heads/} - fi + if [[ "$TAG_VERSION" != "$CODE_VERSION" ]]; then + echo "Version mismatch" + exit 1 fi + echo "Version verified: $TAG_VERSION" + - name: Build package run: | uv build From 6796c339604e48f05e81501d70b7f5f440f56265 Mon Sep 17 00:00:00 2001 From: kramaranya Date: Tue, 26 Aug 2025 13:52:00 +0100 Subject: [PATCH 05/36] Add proper changelog extraction Signed-off-by: kramaranya --- .github/workflows/release.yml | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b5dc3ae3..aafb3c5e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,8 +5,6 @@ on: tags: - 'v*.*.*' - 'v*.*.*-rc.*' - - 'v*.*.*-alpha.*' - - 'v*.*.*-beta.*' permissions: contents: write @@ -108,6 +106,9 @@ jobs: name: Create GitHub Release needs: [build, publish-pypi] runs-on: ubuntu-latest + environment: + name: release + url: https://github.com/kubeflow/sdk/releases steps: - uses: actions/checkout@v4 @@ -120,16 +121,23 @@ jobs: VERSION="${{ needs.build.outputs.version }}" if [[ -f CHANGELOG.md ]]; then - CHANGELOG=$(awk "/^# \[?v?$VERSION\]?/,/^# \[?v?[0-9]/" CHANGELOG.md | sed '$d' | tail -n +2) + NEXT_VERSION=$(sed -n "/^# \[v$VERSION\]/,\$p" CHANGELOG.md | tail -n +2 | grep -m1 "^# \[v[0-9]" || echo "") + if [[ -n "$NEXT_VERSION" ]]; then + CHANGELOG=$(sed -n "/^# \[v$VERSION\]/,/^# \[v[0-9]/p" CHANGELOG.md | sed '$d' | tail -n +2) + else + CHANGELOG=$(sed -n "/^# \[v$VERSION\]/,\$p" CHANGELOG.md | tail -n +2) + fi if [[ -n "$CHANGELOG" ]]; then - echo "changelog<> $GITHUB_OUTPUT - echo "$CHANGELOG" >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT + { + echo "changelog<> $GITHUB_OUTPUT else - echo "**Note:** CHANGELOG.md was not updated for this release" >> $GITHUB_OUTPUT + echo "changelog=**Note:** CHANGELOG.md was not updated for this release" >> $GITHUB_OUTPUT fi else - echo "**Note:** CHANGELOG.md not found" >> $GITHUB_OUTPUT + echo "changelog=**Note:** CHANGELOG.md not found" >> $GITHUB_OUTPUT fi - name: Create GitHub Release From 107d448a0688afeb06a0de8393e690a4d4553af4 Mon Sep 17 00:00:00 2001 From: kramaranya Date: Tue, 26 Aug 2025 13:54:10 +0100 Subject: [PATCH 06/36] Allow to reuse test-python workflow Signed-off-by: kramaranya --- .github/workflows/test-python.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test-python.yaml b/.github/workflows/test-python.yaml index 850bb314..c9d552d7 100644 --- a/.github/workflows/test-python.yaml +++ b/.github/workflows/test-python.yaml @@ -4,6 +4,7 @@ on: pull_request: push: branches: [main] + workflow_call: concurrency: group: ${{ github.workflow }}-${{ github.ref }} From 537df5583abda70a55fe754d537606bc07418517 Mon Sep 17 00:00:00 2001 From: kramaranya Date: Tue, 26 Aug 2025 23:50:27 +0100 Subject: [PATCH 07/36] Add changelog generation script Signed-off-by: kramaranya --- CHANGELOG.md | 1 + pyproject.toml | 1 + scripts/gen-changelog.py | 144 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 146 insertions(+) create mode 100644 CHANGELOG.md create mode 100755 scripts/gen-changelog.py diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..825c32f0 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1 @@ +# Changelog diff --git a/pyproject.toml b/pyproject.toml index 258d5098..661593e6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,6 +40,7 @@ dev = [ "kubeflow_trainer_api@git+https://github.com/kubeflow/trainer.git@master#subdirectory=api/python_api", "ruff>=0.12.2", "pre-commit>=4.2.0", + "PyGithub>=2.7.0", ] [project.urls] diff --git a/scripts/gen-changelog.py b/scripts/gen-changelog.py new file mode 100755 index 00000000..6bbe098a --- /dev/null +++ b/scripts/gen-changelog.py @@ -0,0 +1,144 @@ +import argparse +import sys + +try: + from github import Github +except ImportError: + print("Install PyGithub with pip install -e \".[dev]\"") + sys.exit(1) + +REPO_NAME = "kubeflow/sdk" +CHANGELOG_FILE = "CHANGELOG.md" + + +def categorize_pr(title: str) -> str: + title = title.lower().strip() + if title.startswith('feat'): + return 'feat' + elif title.startswith('fix'): + return 'fix' + elif title.startswith('chore'): + return 'chore' + elif title.startswith('revert'): + return 'revert' + else: + return 'misc' + + +def get_initial_commit(github_repo): + commits = list(github_repo.get_commits()) + return commits[-1].sha + + +def main(): + parser = argparse.ArgumentParser(description="Generate changelog for Kubeflow SDK") + parser.add_argument("--token", required=True, help="GitHub Access Token") + parser.add_argument("--version", required=True, help="Target version (e.g. v0.1.0)") + + args = parser.parse_args() + + current_release = args.version + github_repo = Github(args.token).get_repo(REPO_NAME) + + try: + tags = list(github_repo.get_tags()) + if not tags: + print("First release - using full history") + previous_release = get_initial_commit(github_repo) + else: + previous_release = tags[0].name + print(f"Generating changelog: {previous_release} → HEAD (for {current_release})") + except Exception as e: + print(f"Error finding previous release: {e}") + sys.exit(1) + + comparison = github_repo.compare(previous_release, "HEAD") + commits = list(comparison.commits) + + if not commits: + print("No commits found in range") + sys.exit(1) + + categories = { + 'feat': [], + 'fix': [], + 'chore': [], + 'revert': [], + 'misc': [] + } + + pr_set = set() + for commit in reversed(commits): + for pr in commit.get_pulls(): + if pr.number in pr_set: + continue + pr_set.add(pr.number) + + category = categorize_pr(pr.title) + pr_entry = (f"- {pr.title} ([#{pr.number}]({pr.html_url})) " + f"by [@{pr.user.login}]({pr.user.html_url})") + categories[category].append(pr_entry) + + if not pr_set: + print("No PRs found in range") + sys.exit(1) + + release_date = str(commits[-1].commit.author.date).split(" ")[0] + release_url = f"https://github.com/{REPO_NAME}/releases/tag/{current_release}" + + changelog_content = [ + f"# [{current_release}]({release_url}) ({release_date})\n\n" + ] + + if categories['feat']: + changelog_content.append("## New Features\n\n") + changelog_content.append("\n".join(categories['feat']) + "\n\n") + + if categories['fix']: + changelog_content.append("## Bug Fixes\n\n") + changelog_content.append("\n".join(categories['fix']) + "\n\n") + + if categories['chore']: + changelog_content.append("## Maintenance\n\n") + changelog_content.append("\n".join(categories['chore']) + "\n\n") + + if categories['revert']: + changelog_content.append("## Reverts\n\n") + changelog_content.append("\n".join(categories['revert']) + "\n\n") + + if categories['misc']: + changelog_content.append("## Other Changes\n\n") + changelog_content.append("\n".join(categories['misc']) + "\n\n") + + changelog_content.append(f"**Full Changelog**: {comparison.html_url}\n\n") + + try: + with open(CHANGELOG_FILE, "r") as f: + existing_content = f.read() + except FileNotFoundError: + existing_content = "# Changelog\n\n" + + lines = existing_content.split('\n') + + insert_index = 0 + for i, line in enumerate(lines): + if line.strip() == "# Changelog": + insert_index = i + 1 + while insert_index < len(lines) and not lines[insert_index].strip(): + insert_index += 1 + break + + new_content = ''.join(changelog_content) + new_lines = ( + lines[:insert_index] + [''] + new_content.rstrip().split('\n') + [''] + lines[insert_index:] + ) + + with open(CHANGELOG_FILE, "w") as f: + f.write('\n'.join(new_lines)) + + print("Changelog has been updated") + print(f"Found {len(pr_set)} PRs for {current_release}") + + +if __name__ == "__main__": + main() From 26852d355ee5ce4e78e755f18647dcb4c3378f0f Mon Sep 17 00:00:00 2001 From: kramaranya Date: Tue, 26 Aug 2025 23:57:43 +0100 Subject: [PATCH 08/36] Update uv.lock Signed-off-by: kramaranya --- uv.lock | 177 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) diff --git a/uv.lock b/uv.lock index 12164fd3..09ba6235 100644 --- a/uv.lock +++ b/uv.lock @@ -29,6 +29,75 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618 }, ] +[[package]] +name = "cffi" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/07/f44ca684db4e4f08a3fdc6eeb9a0d15dc6883efc7b8c90357fdbf74e186c/cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14", size = 182191 }, + { url = "https://files.pythonhosted.org/packages/08/fd/cc2fedbd887223f9f5d170c96e57cbf655df9831a6546c1727ae13fa977a/cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67", size = 178592 }, + { url = "https://files.pythonhosted.org/packages/de/cc/4635c320081c78d6ffc2cab0a76025b691a91204f4aa317d568ff9280a2d/cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", size = 426024 }, + { url = "https://files.pythonhosted.org/packages/b6/7b/3b2b250f3aab91abe5f8a51ada1b717935fdaec53f790ad4100fe2ec64d1/cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", size = 448188 }, + { url = "https://files.pythonhosted.org/packages/d3/48/1b9283ebbf0ec065148d8de05d647a986c5f22586b18120020452fff8f5d/cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", size = 455571 }, + { url = "https://files.pythonhosted.org/packages/40/87/3b8452525437b40f39ca7ff70276679772ee7e8b394934ff60e63b7b090c/cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", size = 436687 }, + { url = "https://files.pythonhosted.org/packages/8d/fb/4da72871d177d63649ac449aec2e8a29efe0274035880c7af59101ca2232/cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", size = 446211 }, + { url = "https://files.pythonhosted.org/packages/ab/a0/62f00bcb411332106c02b663b26f3545a9ef136f80d5df746c05878f8c4b/cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", size = 461325 }, + { url = "https://files.pythonhosted.org/packages/36/83/76127035ed2e7e27b0787604d99da630ac3123bfb02d8e80c633f218a11d/cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", size = 438784 }, + { url = "https://files.pythonhosted.org/packages/21/81/a6cd025db2f08ac88b901b745c163d884641909641f9b826e8cb87645942/cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", size = 461564 }, + { url = "https://files.pythonhosted.org/packages/f8/fe/4d41c2f200c4a457933dbd98d3cf4e911870877bd94d9656cc0fcb390681/cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", size = 171804 }, + { url = "https://files.pythonhosted.org/packages/d1/b6/0b0f5ab93b0df4acc49cae758c81fe4e5ef26c3ae2e10cc69249dfd8b3ab/cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", size = 181299 }, + { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264 }, + { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651 }, + { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259 }, + { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200 }, + { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235 }, + { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721 }, + { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242 }, + { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999 }, + { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242 }, + { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604 }, + { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727 }, + { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400 }, + { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178 }, + { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840 }, + { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803 }, + { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850 }, + { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729 }, + { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256 }, + { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424 }, + { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568 }, + { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736 }, + { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448 }, + { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976 }, + { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989 }, + { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802 }, + { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792 }, + { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893 }, + { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810 }, + { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200 }, + { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447 }, + { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358 }, + { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469 }, + { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475 }, + { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009 }, + { url = "https://files.pythonhosted.org/packages/b9/ea/8bb50596b8ffbc49ddd7a1ad305035daa770202a6b782fc164647c2673ad/cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16", size = 182220 }, + { url = "https://files.pythonhosted.org/packages/ae/11/e77c8cd24f58285a82c23af484cf5b124a376b32644e445960d1a4654c3a/cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36", size = 178605 }, + { url = "https://files.pythonhosted.org/packages/ed/65/25a8dc32c53bf5b7b6c2686b42ae2ad58743f7ff644844af7cdb29b49361/cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8", size = 424910 }, + { url = "https://files.pythonhosted.org/packages/42/7a/9d086fab7c66bd7c4d0f27c57a1b6b068ced810afc498cc8c49e0088661c/cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576", size = 447200 }, + { url = "https://files.pythonhosted.org/packages/da/63/1785ced118ce92a993b0ec9e0d0ac8dc3e5dbfbcaa81135be56c69cabbb6/cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87", size = 454565 }, + { url = "https://files.pythonhosted.org/packages/74/06/90b8a44abf3556599cdec107f7290277ae8901a58f75e6fe8f970cd72418/cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0", size = 435635 }, + { url = "https://files.pythonhosted.org/packages/bd/62/a1f468e5708a70b1d86ead5bab5520861d9c7eacce4a885ded9faa7729c3/cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3", size = 445218 }, + { url = "https://files.pythonhosted.org/packages/5b/95/b34462f3ccb09c2594aa782d90a90b045de4ff1f70148ee79c69d37a0a5a/cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595", size = 460486 }, + { url = "https://files.pythonhosted.org/packages/fc/fc/a1e4bebd8d680febd29cf6c8a40067182b64f00c7d105f8f26b5bc54317b/cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a", size = 437911 }, + { url = "https://files.pythonhosted.org/packages/e6/c3/21cab7a6154b6a5ea330ae80de386e7665254835b9e98ecc1340b3a7de9a/cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e", size = 460632 }, + { url = "https://files.pythonhosted.org/packages/cb/b5/fd9f8b5a84010ca169ee49f4e4ad6f8c05f4e3545b72ee041dbbcb159882/cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7", size = 171820 }, + { url = "https://files.pythonhosted.org/packages/8c/52/b08750ce0bce45c143e1b5d7357ee8c55341b52bdef4b0f081af1eb248c2/cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662", size = 181290 }, +] + [[package]] name = "cfgv" version = "3.4.0" @@ -195,6 +264,53 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3c/38/bbe2e63902847cf79036ecc75550d0698af31c91c7575352eb25190d0fb3/coverage-7.9.2-py3-none-any.whl", hash = "sha256:e425cd5b00f6fc0ed7cdbd766c70be8baab4b7839e4d4fe5fac48581dd968ea4", size = 204005 }, ] +[[package]] +name = "cryptography" +version = "45.0.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d6/0d/d13399c94234ee8f3df384819dc67e0c5ce215fb751d567a55a1f4b028c7/cryptography-45.0.6.tar.gz", hash = "sha256:5c966c732cf6e4a276ce83b6e4c729edda2df6929083a952cc7da973c539c719", size = 744949 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8c/29/2793d178d0eda1ca4a09a7c4e09a5185e75738cc6d526433e8663b460ea6/cryptography-45.0.6-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:048e7ad9e08cf4c0ab07ff7f36cc3115924e22e2266e034450a890d9e312dd74", size = 7042702 }, + { url = "https://files.pythonhosted.org/packages/b3/b6/cabd07410f222f32c8d55486c464f432808abaa1f12af9afcbe8f2f19030/cryptography-45.0.6-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:44647c5d796f5fc042bbc6d61307d04bf29bccb74d188f18051b635f20a9c75f", size = 4206483 }, + { url = "https://files.pythonhosted.org/packages/8b/9e/f9c7d36a38b1cfeb1cc74849aabe9bf817990f7603ff6eb485e0d70e0b27/cryptography-45.0.6-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e40b80ecf35ec265c452eea0ba94c9587ca763e739b8e559c128d23bff7ebbbf", size = 4429679 }, + { url = "https://files.pythonhosted.org/packages/9c/2a/4434c17eb32ef30b254b9e8b9830cee4e516f08b47fdd291c5b1255b8101/cryptography-45.0.6-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:00e8724bdad672d75e6f069b27970883179bd472cd24a63f6e620ca7e41cc0c5", size = 4210553 }, + { url = "https://files.pythonhosted.org/packages/ef/1d/09a5df8e0c4b7970f5d1f3aff1b640df6d4be28a64cae970d56c6cf1c772/cryptography-45.0.6-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7a3085d1b319d35296176af31c90338eeb2ddac8104661df79f80e1d9787b8b2", size = 3894499 }, + { url = "https://files.pythonhosted.org/packages/79/62/120842ab20d9150a9d3a6bdc07fe2870384e82f5266d41c53b08a3a96b34/cryptography-45.0.6-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1b7fa6a1c1188c7ee32e47590d16a5a0646270921f8020efc9a511648e1b2e08", size = 4458484 }, + { url = "https://files.pythonhosted.org/packages/fd/80/1bc3634d45ddfed0871bfba52cf8f1ad724761662a0c792b97a951fb1b30/cryptography-45.0.6-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:275ba5cc0d9e320cd70f8e7b96d9e59903c815ca579ab96c1e37278d231fc402", size = 4210281 }, + { url = "https://files.pythonhosted.org/packages/7d/fe/ffb12c2d83d0ee625f124880a1f023b5878f79da92e64c37962bbbe35f3f/cryptography-45.0.6-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:f4028f29a9f38a2025abedb2e409973709c660d44319c61762202206ed577c42", size = 4456890 }, + { url = "https://files.pythonhosted.org/packages/8c/8e/b3f3fe0dc82c77a0deb5f493b23311e09193f2268b77196ec0f7a36e3f3e/cryptography-45.0.6-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ee411a1b977f40bd075392c80c10b58025ee5c6b47a822a33c1198598a7a5f05", size = 4333247 }, + { url = "https://files.pythonhosted.org/packages/b3/a6/c3ef2ab9e334da27a1d7b56af4a2417d77e7806b2e0f90d6267ce120d2e4/cryptography-45.0.6-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e2a21a8eda2d86bb604934b6b37691585bd095c1f788530c1fcefc53a82b3453", size = 4565045 }, + { url = "https://files.pythonhosted.org/packages/31/c3/77722446b13fa71dddd820a5faab4ce6db49e7e0bf8312ef4192a3f78e2f/cryptography-45.0.6-cp311-abi3-win32.whl", hash = "sha256:d063341378d7ee9c91f9d23b431a3502fc8bfacd54ef0a27baa72a0843b29159", size = 2928923 }, + { url = "https://files.pythonhosted.org/packages/38/63/a025c3225188a811b82932a4dcc8457a26c3729d81578ccecbcce2cb784e/cryptography-45.0.6-cp311-abi3-win_amd64.whl", hash = "sha256:833dc32dfc1e39b7376a87b9a6a4288a10aae234631268486558920029b086ec", size = 3403805 }, + { url = "https://files.pythonhosted.org/packages/5b/af/bcfbea93a30809f126d51c074ee0fac5bd9d57d068edf56c2a73abedbea4/cryptography-45.0.6-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:3436128a60a5e5490603ab2adbabc8763613f638513ffa7d311c900a8349a2a0", size = 7020111 }, + { url = "https://files.pythonhosted.org/packages/98/c6/ea5173689e014f1a8470899cd5beeb358e22bb3cf5a876060f9d1ca78af4/cryptography-45.0.6-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0d9ef57b6768d9fa58e92f4947cea96ade1233c0e236db22ba44748ffedca394", size = 4198169 }, + { url = "https://files.pythonhosted.org/packages/ba/73/b12995edc0c7e2311ffb57ebd3b351f6b268fed37d93bfc6f9856e01c473/cryptography-45.0.6-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ea3c42f2016a5bbf71825537c2ad753f2870191134933196bee408aac397b3d9", size = 4421273 }, + { url = "https://files.pythonhosted.org/packages/f7/6e/286894f6f71926bc0da67408c853dd9ba953f662dcb70993a59fd499f111/cryptography-45.0.6-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:20ae4906a13716139d6d762ceb3e0e7e110f7955f3bc3876e3a07f5daadec5f3", size = 4199211 }, + { url = "https://files.pythonhosted.org/packages/de/34/a7f55e39b9623c5cb571d77a6a90387fe557908ffc44f6872f26ca8ae270/cryptography-45.0.6-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dac5ec199038b8e131365e2324c03d20e97fe214af051d20c49db129844e8b3", size = 3883732 }, + { url = "https://files.pythonhosted.org/packages/f9/b9/c6d32edbcba0cd9f5df90f29ed46a65c4631c4fbe11187feb9169c6ff506/cryptography-45.0.6-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:18f878a34b90d688982e43f4b700408b478102dd58b3e39de21b5ebf6509c301", size = 4450655 }, + { url = "https://files.pythonhosted.org/packages/77/2d/09b097adfdee0227cfd4c699b3375a842080f065bab9014248933497c3f9/cryptography-45.0.6-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5bd6020c80c5b2b2242d6c48487d7b85700f5e0038e67b29d706f98440d66eb5", size = 4198956 }, + { url = "https://files.pythonhosted.org/packages/55/66/061ec6689207d54effdff535bbdf85cc380d32dd5377173085812565cf38/cryptography-45.0.6-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:eccddbd986e43014263eda489abbddfbc287af5cddfd690477993dbb31e31016", size = 4449859 }, + { url = "https://files.pythonhosted.org/packages/41/ff/e7d5a2ad2d035e5a2af116e1a3adb4d8fcd0be92a18032917a089c6e5028/cryptography-45.0.6-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:550ae02148206beb722cfe4ef0933f9352bab26b087af00e48fdfb9ade35c5b3", size = 4320254 }, + { url = "https://files.pythonhosted.org/packages/82/27/092d311af22095d288f4db89fcaebadfb2f28944f3d790a4cf51fe5ddaeb/cryptography-45.0.6-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5b64e668fc3528e77efa51ca70fadcd6610e8ab231e3e06ae2bab3b31c2b8ed9", size = 4554815 }, + { url = "https://files.pythonhosted.org/packages/7e/01/aa2f4940262d588a8fdf4edabe4cda45854d00ebc6eaac12568b3a491a16/cryptography-45.0.6-cp37-abi3-win32.whl", hash = "sha256:780c40fb751c7d2b0c6786ceee6b6f871e86e8718a8ff4bc35073ac353c7cd02", size = 2912147 }, + { url = "https://files.pythonhosted.org/packages/0a/bc/16e0276078c2de3ceef6b5a34b965f4436215efac45313df90d55f0ba2d2/cryptography-45.0.6-cp37-abi3-win_amd64.whl", hash = "sha256:20d15aed3ee522faac1a39fbfdfee25d17b1284bafd808e1640a74846d7c4d1b", size = 3390459 }, + { url = "https://files.pythonhosted.org/packages/56/d2/4482d97c948c029be08cb29854a91bd2ae8da7eb9c4152461f1244dcea70/cryptography-45.0.6-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:705bb7c7ecc3d79a50f236adda12ca331c8e7ecfbea51edd931ce5a7a7c4f012", size = 3576812 }, + { url = "https://files.pythonhosted.org/packages/ec/24/55fc238fcaa122855442604b8badb2d442367dfbd5a7ca4bb0bd346e263a/cryptography-45.0.6-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:826b46dae41a1155a0c0e66fafba43d0ede1dc16570b95e40c4d83bfcf0a451d", size = 4141694 }, + { url = "https://files.pythonhosted.org/packages/f9/7e/3ea4fa6fbe51baf3903806a0241c666b04c73d2358a3ecce09ebee8b9622/cryptography-45.0.6-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:cc4d66f5dc4dc37b89cfef1bd5044387f7a1f6f0abb490815628501909332d5d", size = 4375010 }, + { url = "https://files.pythonhosted.org/packages/50/42/ec5a892d82d2a2c29f80fc19ced4ba669bca29f032faf6989609cff1f8dc/cryptography-45.0.6-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:f68f833a9d445cc49f01097d95c83a850795921b3f7cc6488731e69bde3288da", size = 4141377 }, + { url = "https://files.pythonhosted.org/packages/e7/d7/246c4c973a22b9c2931999da953a2c19cae7c66b9154c2d62ffed811225e/cryptography-45.0.6-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:3b5bf5267e98661b9b888a9250d05b063220dfa917a8203744454573c7eb79db", size = 4374609 }, + { url = "https://files.pythonhosted.org/packages/78/6d/c49ccf243f0a1b0781c2a8de8123ee552f0c8a417c6367a24d2ecb7c11b3/cryptography-45.0.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2384f2ab18d9be88a6e4f8972923405e2dbb8d3e16c6b43f15ca491d7831bd18", size = 3322156 }, + { url = "https://files.pythonhosted.org/packages/61/69/c252de4ec047ba2f567ecb53149410219577d408c2aea9c989acae7eafce/cryptography-45.0.6-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fc022c1fa5acff6def2fc6d7819bbbd31ccddfe67d075331a65d9cfb28a20983", size = 3584669 }, + { url = "https://files.pythonhosted.org/packages/e3/fe/deea71e9f310a31fe0a6bfee670955152128d309ea2d1c79e2a5ae0f0401/cryptography-45.0.6-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:3de77e4df42ac8d4e4d6cdb342d989803ad37707cf8f3fbf7b088c9cbdd46427", size = 4153022 }, + { url = "https://files.pythonhosted.org/packages/60/45/a77452f5e49cb580feedba6606d66ae7b82c128947aa754533b3d1bd44b0/cryptography-45.0.6-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:599c8d7df950aa68baa7e98f7b73f4f414c9f02d0e8104a30c0182a07732638b", size = 4386802 }, + { url = "https://files.pythonhosted.org/packages/a3/b9/a2f747d2acd5e3075fdf5c145c7c3568895daaa38b3b0c960ef830db6cdc/cryptography-45.0.6-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:31a2b9a10530a1cb04ffd6aa1cd4d3be9ed49f7d77a4dafe198f3b382f41545c", size = 4152706 }, + { url = "https://files.pythonhosted.org/packages/81/ec/381b3e8d0685a3f3f304a382aa3dfce36af2d76467da0fd4bb21ddccc7b2/cryptography-45.0.6-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:e5b3dda1b00fb41da3af4c5ef3f922a200e33ee5ba0f0bc9ecf0b0c173958385", size = 4386740 }, + { url = "https://files.pythonhosted.org/packages/0a/76/cf8d69da8d0b5ecb0db406f24a63a3f69ba5e791a11b782aeeefef27ccbb/cryptography-45.0.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:629127cfdcdc6806dfe234734d7cb8ac54edaf572148274fa377a7d3405b0043", size = 3331874 }, +] + [[package]] name = "distlib" version = "0.4.0" @@ -289,6 +405,7 @@ dev = [ { name = "coverage" }, { name = "kubeflow-trainer-api" }, { name = "pre-commit" }, + { name = "pygithub" }, { name = "pytest" }, { name = "pytest-mock" }, { name = "ruff" }, @@ -306,6 +423,7 @@ dev = [ { name = "coverage", specifier = ">=7.0" }, { name = "kubeflow-trainer-api", git = "https://github.com/kubeflow/trainer.git?subdirectory=api%2Fpython_api&rev=master" }, { name = "pre-commit", specifier = ">=4.2.0" }, + { name = "pygithub", specifier = ">=2.7.0" }, { name = "pytest", specifier = ">=7.0" }, { name = "pytest-mock", specifier = ">=3.10" }, { name = "ruff", specifier = ">=0.12.2" }, @@ -423,6 +541,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259 }, ] +[[package]] +name = "pycparser" +version = "2.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 }, +] + [[package]] name = "pydantic" version = "2.11.7" @@ -547,6 +674,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d4/29/3cade8a924a61f60ccfa10842f75eb12787e1440e2b8660ceffeb26685e7/pydantic_core-2.33.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2807668ba86cb38c6817ad9bc66215ab8584d1d304030ce4f0887336f28a5e27", size = 2066661 }, ] +[[package]] +name = "pygithub" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyjwt", extra = ["crypto"] }, + { name = "pynacl" }, + { name = "requests" }, + { name = "typing-extensions" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6a/a7/403e04aa96e2d94e1518d518d69718c2ba978c8d3ffa4ab3b101b94dbafa/pygithub-2.7.0.tar.gz", hash = "sha256:7cd6eafabb09b5369afba3586d86b1f1ad6f1326d2ff01bc47bb26615dce4cbb", size = 3707928 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/76/d768dd31322173b3956692b75471ac37bf3759c7abb603152f6a9b6594a8/pygithub-2.7.0-py3-none-any.whl", hash = "sha256:40ecbfe26dc55cc34ab4b0ffa1d455e6f816ef9a2bc8d6f5ad18ce572f163700", size = 416514 }, +] + [[package]] name = "pygments" version = "2.19.2" @@ -556,6 +699,40 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217 }, ] +[[package]] +name = "pyjwt" +version = "2.10.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997 }, +] + +[package.optional-dependencies] +crypto = [ + { name = "cryptography" }, +] + +[[package]] +name = "pynacl" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/22/27582568be639dfe22ddb3902225f91f2f17ceff88ce80e4db396c8986da/PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba", size = 3392854 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/75/0b8ede18506041c0bf23ac4d8e2971b4161cd6ce630b177d0a08eb0d8857/PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1", size = 349920 }, + { url = "https://files.pythonhosted.org/packages/59/bb/fddf10acd09637327a97ef89d2a9d621328850a72f1fdc8c08bdf72e385f/PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92", size = 601722 }, + { url = "https://files.pythonhosted.org/packages/5d/70/87a065c37cca41a75f2ce113a5a2c2aa7533be648b184ade58971b5f7ccc/PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394", size = 680087 }, + { url = "https://files.pythonhosted.org/packages/ee/87/f1bb6a595f14a327e8285b9eb54d41fef76c585a0edef0a45f6fc95de125/PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d", size = 856678 }, + { url = "https://files.pythonhosted.org/packages/66/28/ca86676b69bf9f90e710571b67450508484388bfce09acf8a46f0b8c785f/PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858", size = 1133660 }, + { url = "https://files.pythonhosted.org/packages/3d/85/c262db650e86812585e2bc59e497a8f59948a005325a11bbbc9ecd3fe26b/PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b", size = 663824 }, + { url = "https://files.pythonhosted.org/packages/fd/1a/cc308a884bd299b651f1633acb978e8596c71c33ca85e9dc9fa33a5399b9/PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff", size = 1117912 }, + { url = "https://files.pythonhosted.org/packages/25/2d/b7df6ddb0c2a33afdb358f8af6ea3b8c4d1196ca45497dd37a56f0c122be/PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543", size = 204624 }, + { url = "https://files.pythonhosted.org/packages/5e/22/d3db169895faaf3e2eda892f005f433a62db2decbcfbc2f61e6517adfa87/PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93", size = 212141 }, +] + [[package]] name = "pytest" version = "8.4.1" From 8fc6b4ea47e060039663527f723cc6e3fe911b57 Mon Sep 17 00:00:00 2001 From: kramaranya Date: Wed, 27 Aug 2025 07:17:04 +0100 Subject: [PATCH 09/36] Remove blank lines in changelog script Signed-off-by: kramaranya --- scripts/gen-changelog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/gen-changelog.py b/scripts/gen-changelog.py index 6bbe098a..49f311b0 100755 --- a/scripts/gen-changelog.py +++ b/scripts/gen-changelog.py @@ -130,7 +130,7 @@ def main(): new_content = ''.join(changelog_content) new_lines = ( - lines[:insert_index] + [''] + new_content.rstrip().split('\n') + [''] + lines[insert_index:] + lines[:insert_index] + new_content.rstrip().split('\n') + [''] + lines[insert_index:] ) with open(CHANGELOG_FILE, "w") as f: From 371c882d1936e35bdddc7892eff440d9e41970db Mon Sep 17 00:00:00 2001 From: kramaranya Date: Wed, 27 Aug 2025 08:44:02 +0100 Subject: [PATCH 10/36] Add make release in Makefile Signed-off-by: kramaranya --- Makefile | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Makefile b/Makefile index 507b04d5..7617d085 100644 --- a/Makefile +++ b/Makefile @@ -64,6 +64,13 @@ uv-venv: echo "uv virtual environment already exists in $(VENV_DIR)."; \ fi +.PHONY: release +release: uv-venv + @if [ -z "$(VERSION)" ]; then echo "Usage: make release VERSION=v0.1.0"; exit 1; fi + @VERSION_NUM=$$(echo "$(VERSION)" | sed 's/^v//'); \ + sed -i '' "s/__version__ = \".*\"/__version__ = \"$$VERSION_NUM\"/" kubeflow/__init__.py; \ + uv run python scripts/gen-changelog.py --token=$${GITHUB_TOKEN} --version=$(VERSION) + # make test-python will produce html coverage by default. Run with `make test-python report=xml` to produce xml report. .PHONY: test-python test-python: uv-venv From f7a5856dd3154b5250c9c27c2e37ba19fc687c89 Mon Sep 17 00:00:00 2001 From: kramaranya Date: Wed, 3 Sep 2025 23:39:04 +0100 Subject: [PATCH 11/36] Refactor gen_changelog Signed-off-by: kramaranya --- CHANGELOG.md | 49 ++++++++++++++++++++++++++++++++++++++++ scripts/gen-changelog.py | 38 +++++++++++++++---------------- 2 files changed, 68 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 825c32f0..d7bdc7b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,50 @@ # Changelog + +# [v0.5.0](https://github.com/kubeflow/sdk/releases/tag/v0.5.0) (2025-09-01) + +## New Features + +- feat: Implement Kubernetes Backend ([#68](https://github.com/kubeflow/sdk/pull/68)) by [@szaher](https://github.com/szaher) +- feat(docs): add ROADMAP of Kubeflow SDK ([#44](https://github.com/kubeflow/sdk/pull/44)) by [@kramaranya](https://github.com/kramaranya) +- feat(trainer): Add `get_runtime_packages()` API ([#57](https://github.com/kubeflow/sdk/pull/57)) by [@andreyvelich](https://github.com/andreyvelich) +- feat(trainer): Support Framework Labels in Runtimes ([#56](https://github.com/kubeflow/sdk/pull/56)) by [@andreyvelich](https://github.com/andreyvelich) +- feat(trainer): Add environment variables argument to CustomTrainer ([#54](https://github.com/kubeflow/sdk/pull/54)) by [@astefanutti](https://github.com/astefanutti) +- feat(trainer): Add `wait_for_job_status()` API ([#52](https://github.com/kubeflow/sdk/pull/52)) by [@andreyvelich](https://github.com/andreyvelich) +- feat(ci): Add GitHub action to verify PR titles ([#42](https://github.com/kubeflow/sdk/pull/42)) by [@andreyvelich](https://github.com/andreyvelich) + +## Bug Fixes + +- fix: trainer client backend public ([#78](https://github.com/kubeflow/sdk/pull/78)) by [@jaiakash](https://github.com/jaiakash) +- fix(trainer): Keep the original runtime command in get_runtime_packages() API ([#64](https://github.com/kubeflow/sdk/pull/64)) by [@andreyvelich](https://github.com/andreyvelich) +- fix(trainer): fix __all__ import. ([#43](https://github.com/kubeflow/sdk/pull/43)) by [@Electronic-Waste](https://github.com/Electronic-Waste) +- fix: Expose BuiltinTrainer API to users ([#28](https://github.com/kubeflow/sdk/pull/28)) by [@Electronic-Waste](https://github.com/Electronic-Waste) + +## Maintenance + +- chore(trainer): Use explicit exception chaining ([#80](https://github.com/kubeflow/sdk/pull/80)) by [@andreyvelich](https://github.com/andreyvelich) +- chore: Nominate @kramaranya and @szaher as Kubeflow SDK reviewers ([#76](https://github.com/kubeflow/sdk/pull/76)) by [@andreyvelich](https://github.com/andreyvelich) +- chore: Enable parallel builds for coveralls ([#81](https://github.com/kubeflow/sdk/pull/81)) by [@kramaranya](https://github.com/kramaranya) +- chore: Remove tool.hatch.build.targets from pyproject ([#73](https://github.com/kubeflow/sdk/pull/73)) by [@kramaranya](https://github.com/kramaranya) +- chore: Move dev extras to dependency-groups ([#71](https://github.com/kubeflow/sdk/pull/71)) by [@kramaranya](https://github.com/kramaranya) +- chore: Update README.md ([#67](https://github.com/kubeflow/sdk/pull/67)) by [@kramaranya](https://github.com/kramaranya) +- chore: move pyproject.toml to root ([#61](https://github.com/kubeflow/sdk/pull/61)) by [@kramaranya](https://github.com/kramaranya) +- chore(ci): Align Kubernetes versions from Trainer for e2e tests ([#58](https://github.com/kubeflow/sdk/pull/58)) by [@astefanutti](https://github.com/astefanutti) +- chore(ci): Add dev tests with master dependencies ([#55](https://github.com/kubeflow/sdk/pull/55)) by [@kramaranya](https://github.com/kramaranya) +- chore(docs): Add Coveralls Badge to the README ([#53](https://github.com/kubeflow/sdk/pull/53)) by [@andreyvelich](https://github.com/andreyvelich) +- chore(trainer): Remove accelerator label from the runtimes ([#51](https://github.com/kubeflow/sdk/pull/51)) by [@andreyvelich](https://github.com/andreyvelich) + +## Other Changes + +- add unit test for trainer sdk ([#17](https://github.com/kubeflow/sdk/pull/17)) by [@briangallagher](https://github.com/briangallagher) +- add e2e notebook tests ([#27](https://github.com/kubeflow/sdk/pull/27)) by [@briangallagher](https://github.com/briangallagher) +- Update pyproject.toml project links ([#40](https://github.com/kubeflow/sdk/pull/40)) by [@szaher](https://github.com/szaher) +- Add support for UV & Ruff ([#38](https://github.com/kubeflow/sdk/pull/38)) by [@szaher](https://github.com/szaher) +- Step down from sdk ownership role ([#37](https://github.com/kubeflow/sdk/pull/37)) by [@tenzen-y](https://github.com/tenzen-y) +- Add CONTRIBUTING.md ([#30](https://github.com/kubeflow/sdk/pull/30)) by [@abhijeet-dhumal](https://github.com/abhijeet-dhumal) +- Reflect owners updates from KF Trainer ([#32](https://github.com/kubeflow/sdk/pull/32)) by [@tenzen-y](https://github.com/tenzen-y) +- Consume Trainer models from external package kubeflow_trainer_api ([#15](https://github.com/kubeflow/sdk/pull/15)) by [@kramaranya](https://github.com/kramaranya) +- Add pre-commit and flake8 configs ([#6](https://github.com/kubeflow/sdk/pull/6)) by [@eoinfennessy](https://github.com/eoinfennessy) +- Add Stale GitHub action ([#7](https://github.com/kubeflow/sdk/pull/7)) by [@kramaranya](https://github.com/kramaranya) +- Add GitHub issue and PR templates ([#5](https://github.com/kubeflow/sdk/pull/5)) by [@eoinfennessy](https://github.com/eoinfennessy) + +**Full Changelog**: https://github.com/kubeflow/sdk/compare/821e01f2b2a96204f851e27fd18ae02e8d876aa7...HEAD diff --git a/scripts/gen-changelog.py b/scripts/gen-changelog.py index 49f311b0..cd7452e4 100755 --- a/scripts/gen-changelog.py +++ b/scripts/gen-changelog.py @@ -1,14 +1,15 @@ import argparse +import os import sys try: from github import Github except ImportError: - print("Install PyGithub with pip install -e \".[dev]\"") + print("Install PyGithub with uv sync") sys.exit(1) REPO_NAME = "kubeflow/sdk" -CHANGELOG_FILE = "CHANGELOG.md" +CHANGELOG_DIR = "CHANGELOG" def categorize_pr(title: str) -> str: @@ -47,7 +48,7 @@ def main(): previous_release = get_initial_commit(github_repo) else: previous_release = tags[0].name - print(f"Generating changelog: {previous_release} → HEAD (for {current_release})") + print(f"Generating changelog: {previous_release} → {current_release}") except Exception as e: print(f"Error finding previous release: {e}") sys.exit(1) @@ -86,6 +87,12 @@ def main(): release_date = str(commits[-1].commit.author.date).split(" ")[0] release_url = f"https://github.com/{REPO_NAME}/releases/tag/{current_release}" + major_minor_parts = current_release.lstrip('v').split('.')[:2] + major_minor = '.'.join(major_minor_parts) + changelog_file = os.path.join(CHANGELOG_DIR, f"CHANGELOG-{major_minor}.md") + + os.makedirs(CHANGELOG_DIR, exist_ok=True) + changelog_content = [ f"# [{current_release}]({release_url}) ({release_date})\n\n" ] @@ -113,30 +120,23 @@ def main(): changelog_content.append(f"**Full Changelog**: {comparison.html_url}\n\n") try: - with open(CHANGELOG_FILE, "r") as f: + with open(changelog_file, "r") as f: existing_content = f.read() except FileNotFoundError: - existing_content = "# Changelog\n\n" + existing_content = "" - lines = existing_content.split('\n') - - insert_index = 0 - for i, line in enumerate(lines): - if line.strip() == "# Changelog": - insert_index = i + 1 - while insert_index < len(lines) and not lines[insert_index].strip(): - insert_index += 1 - break + lines = existing_content.split('\n') if existing_content else [] new_content = ''.join(changelog_content) - new_lines = ( - lines[:insert_index] + new_content.rstrip().split('\n') + [''] + lines[insert_index:] - ) + if lines: + new_lines = new_content.rstrip().split('\n') + [''] + lines + else: + new_lines = new_content.rstrip().split('\n') - with open(CHANGELOG_FILE, "w") as f: + with open(changelog_file, "w") as f: f.write('\n'.join(new_lines)) - print("Changelog has been updated") + print(f"Changelog has been updated: {changelog_file}") print(f"Found {len(pr_set)} PRs for {current_release}") From 6c5183354f8253adf1738e8cc44e6da310145a13 Mon Sep 17 00:00:00 2001 From: kramaranya Date: Wed, 3 Sep 2025 23:40:44 +0100 Subject: [PATCH 12/36] Delete CHANGELOG.md Signed-off-by: kramaranya --- CHANGELOG.md | 50 -------------------------------------------------- 1 file changed, 50 deletions(-) delete mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index d7bdc7b8..00000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,50 +0,0 @@ -# Changelog - -# [v0.5.0](https://github.com/kubeflow/sdk/releases/tag/v0.5.0) (2025-09-01) - -## New Features - -- feat: Implement Kubernetes Backend ([#68](https://github.com/kubeflow/sdk/pull/68)) by [@szaher](https://github.com/szaher) -- feat(docs): add ROADMAP of Kubeflow SDK ([#44](https://github.com/kubeflow/sdk/pull/44)) by [@kramaranya](https://github.com/kramaranya) -- feat(trainer): Add `get_runtime_packages()` API ([#57](https://github.com/kubeflow/sdk/pull/57)) by [@andreyvelich](https://github.com/andreyvelich) -- feat(trainer): Support Framework Labels in Runtimes ([#56](https://github.com/kubeflow/sdk/pull/56)) by [@andreyvelich](https://github.com/andreyvelich) -- feat(trainer): Add environment variables argument to CustomTrainer ([#54](https://github.com/kubeflow/sdk/pull/54)) by [@astefanutti](https://github.com/astefanutti) -- feat(trainer): Add `wait_for_job_status()` API ([#52](https://github.com/kubeflow/sdk/pull/52)) by [@andreyvelich](https://github.com/andreyvelich) -- feat(ci): Add GitHub action to verify PR titles ([#42](https://github.com/kubeflow/sdk/pull/42)) by [@andreyvelich](https://github.com/andreyvelich) - -## Bug Fixes - -- fix: trainer client backend public ([#78](https://github.com/kubeflow/sdk/pull/78)) by [@jaiakash](https://github.com/jaiakash) -- fix(trainer): Keep the original runtime command in get_runtime_packages() API ([#64](https://github.com/kubeflow/sdk/pull/64)) by [@andreyvelich](https://github.com/andreyvelich) -- fix(trainer): fix __all__ import. ([#43](https://github.com/kubeflow/sdk/pull/43)) by [@Electronic-Waste](https://github.com/Electronic-Waste) -- fix: Expose BuiltinTrainer API to users ([#28](https://github.com/kubeflow/sdk/pull/28)) by [@Electronic-Waste](https://github.com/Electronic-Waste) - -## Maintenance - -- chore(trainer): Use explicit exception chaining ([#80](https://github.com/kubeflow/sdk/pull/80)) by [@andreyvelich](https://github.com/andreyvelich) -- chore: Nominate @kramaranya and @szaher as Kubeflow SDK reviewers ([#76](https://github.com/kubeflow/sdk/pull/76)) by [@andreyvelich](https://github.com/andreyvelich) -- chore: Enable parallel builds for coveralls ([#81](https://github.com/kubeflow/sdk/pull/81)) by [@kramaranya](https://github.com/kramaranya) -- chore: Remove tool.hatch.build.targets from pyproject ([#73](https://github.com/kubeflow/sdk/pull/73)) by [@kramaranya](https://github.com/kramaranya) -- chore: Move dev extras to dependency-groups ([#71](https://github.com/kubeflow/sdk/pull/71)) by [@kramaranya](https://github.com/kramaranya) -- chore: Update README.md ([#67](https://github.com/kubeflow/sdk/pull/67)) by [@kramaranya](https://github.com/kramaranya) -- chore: move pyproject.toml to root ([#61](https://github.com/kubeflow/sdk/pull/61)) by [@kramaranya](https://github.com/kramaranya) -- chore(ci): Align Kubernetes versions from Trainer for e2e tests ([#58](https://github.com/kubeflow/sdk/pull/58)) by [@astefanutti](https://github.com/astefanutti) -- chore(ci): Add dev tests with master dependencies ([#55](https://github.com/kubeflow/sdk/pull/55)) by [@kramaranya](https://github.com/kramaranya) -- chore(docs): Add Coveralls Badge to the README ([#53](https://github.com/kubeflow/sdk/pull/53)) by [@andreyvelich](https://github.com/andreyvelich) -- chore(trainer): Remove accelerator label from the runtimes ([#51](https://github.com/kubeflow/sdk/pull/51)) by [@andreyvelich](https://github.com/andreyvelich) - -## Other Changes - -- add unit test for trainer sdk ([#17](https://github.com/kubeflow/sdk/pull/17)) by [@briangallagher](https://github.com/briangallagher) -- add e2e notebook tests ([#27](https://github.com/kubeflow/sdk/pull/27)) by [@briangallagher](https://github.com/briangallagher) -- Update pyproject.toml project links ([#40](https://github.com/kubeflow/sdk/pull/40)) by [@szaher](https://github.com/szaher) -- Add support for UV & Ruff ([#38](https://github.com/kubeflow/sdk/pull/38)) by [@szaher](https://github.com/szaher) -- Step down from sdk ownership role ([#37](https://github.com/kubeflow/sdk/pull/37)) by [@tenzen-y](https://github.com/tenzen-y) -- Add CONTRIBUTING.md ([#30](https://github.com/kubeflow/sdk/pull/30)) by [@abhijeet-dhumal](https://github.com/abhijeet-dhumal) -- Reflect owners updates from KF Trainer ([#32](https://github.com/kubeflow/sdk/pull/32)) by [@tenzen-y](https://github.com/tenzen-y) -- Consume Trainer models from external package kubeflow_trainer_api ([#15](https://github.com/kubeflow/sdk/pull/15)) by [@kramaranya](https://github.com/kramaranya) -- Add pre-commit and flake8 configs ([#6](https://github.com/kubeflow/sdk/pull/6)) by [@eoinfennessy](https://github.com/eoinfennessy) -- Add Stale GitHub action ([#7](https://github.com/kubeflow/sdk/pull/7)) by [@kramaranya](https://github.com/kramaranya) -- Add GitHub issue and PR templates ([#5](https://github.com/kubeflow/sdk/pull/5)) by [@eoinfennessy](https://github.com/eoinfennessy) - -**Full Changelog**: https://github.com/kubeflow/sdk/compare/821e01f2b2a96204f851e27fd18ae02e8d876aa7...HEAD From 9dbecaaefda8b9a9917fd7079e68723fceea94f1 Mon Sep 17 00:00:00 2001 From: kramaranya Date: Thu, 4 Sep 2025 00:06:41 +0100 Subject: [PATCH 13/36] Add prepare-release CI Signed-off-by: kramaranya --- .github/workflows/prepare-release.yml | 66 ++++++++++++++++++ .github/workflows/release.yml | 97 ++++++++++++++++++--------- 2 files changed, 133 insertions(+), 30 deletions(-) create mode 100644 .github/workflows/prepare-release.yml diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml new file mode 100644 index 00000000..9f1aa9fa --- /dev/null +++ b/.github/workflows/prepare-release.yml @@ -0,0 +1,66 @@ +name: Prepare Release + +on: + push: + branches: [ main ] + paths: + - 'kubeflow/__init__.py' + +permissions: + contents: write + +jobs: + prepare: + name: Create release branch + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Configure git user + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + - name: Check version and branch + id: vars + shell: bash + run: | + VERSION=v$(sed -n 's/^__version__ = "\(.*\)"/\1/p' kubeflow/__init__.py) + MAJOR_MINOR=$(echo "$VERSION" | sed 's/^v//' | cut -d. -f1,2) + BRANCH=release-$MAJOR_MINOR + + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "branch=$BRANCH" >> $GITHUB_OUTPUT + + - name: Check if tag already exists + shell: bash + run: | + if git ls-remote --tags origin "${{ steps.vars.outputs.version }}" | grep -q "refs/tags/${{ steps.vars.outputs.version }}"; then + echo "Tag ${{ steps.vars.outputs.version }} already exists. Skipping CI."; exit 0; fi + + - name: Ensure release branch exists and contains version bump + shell: bash + run: | + set -euo pipefail + VERSION="${{ steps.vars.outputs.version }}" + BRANCH="${{ steps.vars.outputs.branch }}" + + MAIN_SHA="${{ github.sha }}" + + if git ls-remote --heads origin "$BRANCH" | grep -q "$BRANCH"; then + echo "Using existing branch: $BRANCH" + git fetch origin "$BRANCH":"$BRANCH" + git checkout "$BRANCH" + if ! git cherry-pick -x "$MAIN_SHA"; then + echo "Cherry-pick failed. Please resolve manually on $BRANCH." >&2 + exit 1 + fi + else + echo "Creating new branch: $BRANCH from main@${MAIN_SHA}" + git checkout -B "$BRANCH" "$MAIN_SHA" + fi + + git push origin "$BRANCH" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index aafb3c5e..ac4f264d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,23 +1,28 @@ name: Release on: - push: - tags: - - 'v*.*.*' - - 'v*.*.*-rc.*' + workflow_run: + workflows: ["Prepare Release"] + types: + - completed permissions: contents: write id-token: write jobs: - test: - name: Run Tests - uses: ./.github/workflows/test-python.yaml - + guard: + if: ${{ github.event.workflow_run.conclusion == 'success' }} + runs-on: ubuntu-latest + outputs: + proceed: ${{ steps.set.outputs.proceed }} + steps: + - id: set + run: echo "proceed=true" >> $GITHUB_OUTPUT build: name: Build Package - needs: [test] + needs: [guard] + if: needs.guard.outputs.proceed == 'true' runs-on: ubuntu-latest outputs: version: ${{ steps.version.outputs.version }} @@ -30,33 +35,42 @@ jobs: fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.11' - - - name: Setup build environment - run: | - make verify - - name: Extract version info id: version run: | - VERSION=${GITHUB_REF#refs/tags/v} - TAG=${GITHUB_REF#refs/tags/} + VERSION=v$(python -c "import kubeflow; print(kubeflow.__version__)" ) + TAG=$VERSION echo "version=$VERSION" >> $GITHUB_OUTPUT echo "tag=$TAG" >> $GITHUB_OUTPUT - if [[ "$VERSION" =~ -rc\.|alpha|beta ]]; then + if [[ "$VERSION" =~ -rc($|\.) ]]; then echo "is-prerelease=true" >> $GITHUB_OUTPUT else echo "is-prerelease=false" >> $GITHUB_OUTPUT fi + - name: Checkout release branch + run: | + VERSION="${{ steps.version.outputs.version }}" + MAJOR_MINOR=$(echo "$VERSION" | sed 's/^v//' | cut -d. -f1,2) + BRANCH="release-$MAJOR_MINOR" + echo "Switching to $BRANCH" + git fetch origin "$BRANCH":"$BRANCH" || true + git checkout "$BRANCH" + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Setup build environment + run: | + make verify + - name: Verify version run: | TAG_VERSION="${{ steps.version.outputs.version }}" - CODE_VERSION=$(python -c "import kubeflow; print(kubeflow.__version__)") + CODE_VERSION="v$(python -c "import kubeflow; print(kubeflow.__version__)")" echo "Tag version: $TAG_VERSION" echo "Code version: $CODE_VERSION" @@ -82,9 +96,29 @@ jobs: name: dist-${{ steps.version.outputs.version }} path: dist/ + create-tag: + name: Create and Push Tag + needs: [build] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Create tag + run: | + VERSION="${{ needs.build.outputs.version }}" + if git ls-remote --tags origin "$VERSION" | grep -q "refs/tags/$VERSION"; then + echo "Tag $VERSION already exists. Skipping"; exit 0; fi + MAJOR_MINOR=$(echo "$VERSION" | sed 's/^v//' | cut -d. -f1,2) + BRANCH="release-$MAJOR_MINOR" + git fetch origin "$BRANCH":"$BRANCH" + git checkout "$BRANCH" + git tag "$VERSION" + git push origin "$VERSION" + publish-pypi: name: Publish to PyPI - needs: [build] + needs: [build, create-tag] runs-on: ubuntu-latest environment: name: release @@ -104,7 +138,7 @@ jobs: github-release: name: Create GitHub Release - needs: [build, publish-pypi] + needs: [build, create-tag, publish-pypi] runs-on: ubuntu-latest environment: name: release @@ -119,13 +153,16 @@ jobs: id: changelog run: | VERSION="${{ needs.build.outputs.version }}" + MAJOR_MINOR=$(echo "$VERSION" | sed 's/^v//' | cut -d. -f1,2) + CHANGELOG_FILE="CHANGELOG/CHANGELOG-${MAJOR_MINOR}.md" - if [[ -f CHANGELOG.md ]]; then - NEXT_VERSION=$(sed -n "/^# \[v$VERSION\]/,\$p" CHANGELOG.md | tail -n +2 | grep -m1 "^# \[v[0-9]" || echo "") + if [[ -f "$CHANGELOG_FILE" ]]; then + HEADER_REGEX="^# \\[${VERSION//./\\.}\\]" + NEXT_VERSION=$(sed -n "/$HEADER_REGEX/,$p" "$CHANGELOG_FILE" | tail -n +2 | grep -m1 "^# \\[v[0-9]" || echo "") if [[ -n "$NEXT_VERSION" ]]; then - CHANGELOG=$(sed -n "/^# \[v$VERSION\]/,/^# \[v[0-9]/p" CHANGELOG.md | sed '$d' | tail -n +2) + CHANGELOG=$(sed -n "/$HEADER_REGEX/,/^# \\[v[0-9]/p" "$CHANGELOG_FILE" | sed '1d;$d') else - CHANGELOG=$(sed -n "/^# \[v$VERSION\]/,\$p" CHANGELOG.md | tail -n +2) + CHANGELOG=$(sed -n "/$HEADER_REGEX/,$p" "$CHANGELOG_FILE" | sed '1d') fi if [[ -n "$CHANGELOG" ]]; then { @@ -134,10 +171,10 @@ jobs: echo "EOF" } >> $GITHUB_OUTPUT else - echo "changelog=**Note:** CHANGELOG.md was not updated for this release" >> $GITHUB_OUTPUT + echo "changelog=**Note:** No changelog section found for $VERSION in $CHANGELOG_FILE" >> $GITHUB_OUTPUT fi else - echo "changelog=**Note:** CHANGELOG.md not found" >> $GITHUB_OUTPUT + echo "changelog=**Note:** $CHANGELOG_FILE not found" >> $GITHUB_OUTPUT fi - name: Create GitHub Release From 3592ce4fa2360970a4052e954aa534ed55bafa9c Mon Sep 17 00:00:00 2001 From: kramaranya Date: Thu, 4 Sep 2025 07:22:14 +0100 Subject: [PATCH 14/36] Allow manual trigger of release CI Signed-off-by: kramaranya --- .github/workflows/prepare-release.yml | 1 + .github/workflows/release.yml | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index 9f1aa9fa..32c951c7 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -5,6 +5,7 @@ on: branches: [ main ] paths: - 'kubeflow/__init__.py' + workflow_dispatch: {} permissions: contents: write diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ac4f264d..7267a0f0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,6 +5,7 @@ on: workflows: ["Prepare Release"] types: - completed + workflow_dispatch: {} permissions: contents: write @@ -12,7 +13,7 @@ permissions: jobs: guard: - if: ${{ github.event.workflow_run.conclusion == 'success' }} + if: ${{ (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') || github.event_name == 'workflow_dispatch' }} runs-on: ubuntu-latest outputs: proceed: ${{ steps.set.outputs.proceed }} From 998e6968b07f546192cb4cbd89660a1dd3b64ad4 Mon Sep 17 00:00:00 2001 From: kramaranya Date: Thu, 4 Sep 2025 07:54:18 +0100 Subject: [PATCH 15/36] Add release readme Signed-off-by: kramaranya --- docs/release/README.md | 138 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 docs/release/README.md diff --git a/docs/release/README.md b/docs/release/README.md new file mode 100644 index 00000000..4c942e7f --- /dev/null +++ b/docs/release/README.md @@ -0,0 +1,138 @@ +# Releasing the Kubeflow SDK + +## Prerequisites + +- [Write](https://docs.github.com/en/organizations/managing-access-to-your-organizations-repositories/repository-permission-levels-for-an-organization#permission-levels-for-repositories-owned-by-an-organization) + permission for the Kubeflow SDK repository. + +- Create a [GitHub Token](https://docs.github.com/en/github/authenticating-to-github/keeping-your-account-and-data-secure/creating-a-personal-access-token) and set it as `GITHUB_TOKEN` environment variable. + +- Install development dependencies: + + ```bash + uv sync + ``` + +## Versioning Policy + +Kubeflow SDK version format follows [Semantic Versioning](https://semver.org/). +Kubeflow SDK versions are in the format of `vX.Y.Z`, where `X` is the major version, `Y` is +the minor version, and `Z` is the patch version. +The patch version contains only bug fixes. + +Additionally, Kubeflow SDK does pre-releases in this format: `vX.Y.Z-rc.N` where `N` is a number +of the `Nth` release candidate (RC) before an upcoming public release named `vX.Y.Z`. + +## Release Branches and Tags + +Kubeflow SDK releases are tagged with tags like `vX.Y.Z`, for example `v0.1.0`. + +Release branches are in the format of `release-X.Y`, where `X.Y` stands for +the minor release. + +`vX.Y.Z` releases are released from the `release-X.Y` branch. For example, +`v0.1.0` release should be on `release-0.1` branch. + +If you want to push changes to the `release-X.Y` release branch, you have to +cherry pick your changes from the `main` branch and submit a PR. + +## Changelog Structure + +Kubeflow SDK uses a directory-based changelog structure under `CHANGELOG/`: + +``` +CHANGELOG/ +├── CHANGELOG-0.1.md # All 0.1.x releases +├── CHANGELOG-0.2.md # All 0.2.x releases +└── CHANGELOG-0.3.md # All 0.3.x releases +``` + +Each file contains releases for that minor series, with the most recent releases at the top. + +## Release Process + +### Automated Release Workflow + +The Kubeflow SDK uses an automated release process with GitHub Actions: + +1. **Local Preparation**: Update version and generate changelog locally +2. **Automated CI**: GitHub Actions handles branch creation, tagging, building, and publishing +3. **Manual Approvals**: PyPI and GitHub releases require manual approval + +### Step-by-Step Release Process + +#### 1. Update Version and Changelog + +1. Generate version and changelog locally: + + ```sh + export GITHUB_TOKEN= + make release VERSION=vX.Y.Z + ``` + +This updates: +- `kubeflow/__init__.py` with `__version__ = "X.Y.Z"` +- `CHANGELOG/CHANGELOG-X.Y.md` with a new top entry `# [vX.Y.Z] (YYYY-MM-DD)` + +2. Open a PR: + - Review `kubeflow/__init__.py` and `CHANGELOG/CHANGELOG-X.Y.md` + - Open a PR to `main` and get it reviewed and merged + +#### 2. Automated Release Branch Creation + +The `Prepare Release` GitHub Action automatically: + +- Detects the version change in `kubeflow/__init__.py` +- Creates or updates the `release-X.Y` branch +- Cherry-picks the version bump commit from main + +**Verification**: Confirm the release branch was created/updated! + +#### 3. Automated Release Process + +The `Release` GitHub Action automatically: + +- Runs tests and builds the package +- Creates and pushes the release tag +- Publishes to PyPI (requires manual approval) +- Creates GitHub Release (requires manual approval) + +**Verification**: Confirm the release tag was created! + +#### 4. Manual Approvals + +1. **PyPI Publishing**: Go to [GitHub Actions](https://github.com/kubeflow/sdk/actions) → `Release` workflow → Approve "Publish to PyPI" + +2. **GitHub Release**: After PyPI approval → Approve "Create GitHub Release" + +#### 5. Final Verification + +1. Verify the release on [PyPI](https://pypi.org/project/kubeflow/) +2. Verify the release on [GitHub Releases](https://github.com/kubeflow/sdk/releases) +3. Test installation: `pip install kubeflow==X.Y.Z` + +### Release Types + +#### Major/Minor Release (vX.Y.0) + +- Creates new `release-X.Y` branch +- Uses full changelog since previous minor version +- Example: `make release VERSION=v0.2.0` + +#### Patch Release (vX.Y.Z) + +- Updates existing `release-X.Y` branch +- Cherry-pick fixes from main to release branch +- Example: `make release VERSION=v0.2.1` + +#### Release Candidate (vX.Y.Z-rc.N) + +- Creates pre-release +- GitHub Release marked as "pre-release" +- Example: `make release VERSION=v0.2.0-rc.1` + +## Announcement + +**Announce**: Post the announcement for the new Kubeflow SDK release in: +- [#kubeflow-ml-experience](https://www.kubeflow.org/docs/about/community/#slack-channels) Slack channel +- [kubeflow-discuss](https://www.kubeflow.org/docs/about/community/#kubeflow-mailing-list) mailing list From e3740aceaa7dc9a02aeb11ece7ca7c95cb51c43a Mon Sep 17 00:00:00 2001 From: kramaranya Date: Thu, 4 Sep 2025 09:04:06 +0100 Subject: [PATCH 16/36] Update chnagelog parser in CI Signed-off-by: kramaranya --- .github/workflows/release.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7267a0f0..e38b1187 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -158,12 +158,14 @@ jobs: CHANGELOG_FILE="CHANGELOG/CHANGELOG-${MAJOR_MINOR}.md" if [[ -f "$CHANGELOG_FILE" ]]; then + set -euo pipefail HEADER_REGEX="^# \\[${VERSION//./\\.}\\]" - NEXT_VERSION=$(sed -n "/$HEADER_REGEX/,$p" "$CHANGELOG_FILE" | tail -n +2 | grep -m1 "^# \\[v[0-9]" || echo "") + SECTION=$(sed -n "/$HEADER_REGEX/,\$p" "$CHANGELOG_FILE" | tail -n +2) + NEXT_VERSION=$(echo "$SECTION" | grep -m1 "^# \\[v[0-9]" || true) if [[ -n "$NEXT_VERSION" ]]; then - CHANGELOG=$(sed -n "/$HEADER_REGEX/,/^# \\[v[0-9]/p" "$CHANGELOG_FILE" | sed '1d;$d') + CHANGELOG=$(echo "$SECTION" | sed -n "1,/^# \\[v[0-9]/p" | sed '1d;$d') else - CHANGELOG=$(sed -n "/$HEADER_REGEX/,$p" "$CHANGELOG_FILE" | sed '1d') + CHANGELOG=$(echo "$SECTION" | sed '1d') fi if [[ -n "$CHANGELOG" ]]; then { From fbf3bba5ddc629aa3605c3c99cbcc7987baf4620 Mon Sep 17 00:00:00 2001 From: kramaranya Date: Sat, 6 Sep 2025 09:22:42 +0100 Subject: [PATCH 17/36] skip and check cherry pick for new release Signed-off-by: kramaranya --- .github/workflows/prepare-release.yml | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index 32c951c7..782c806b 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -37,12 +37,17 @@ jobs: echo "branch=$BRANCH" >> $GITHUB_OUTPUT - name: Check if tag already exists + id: tagcheck shell: bash run: | if git ls-remote --tags origin "${{ steps.vars.outputs.version }}" | grep -q "refs/tags/${{ steps.vars.outputs.version }}"; then - echo "Tag ${{ steps.vars.outputs.version }} already exists. Skipping CI."; exit 0; fi + echo "tag_exists=true" >> "$GITHUB_OUTPUT" + else + echo "tag_exists=false" >> "$GITHUB_OUTPUT" + fi - name: Ensure release branch exists and contains version bump + if: steps.tagcheck.outputs.tag_exists == 'false' shell: bash run: | set -euo pipefail @@ -55,9 +60,13 @@ jobs: echo "Using existing branch: $BRANCH" git fetch origin "$BRANCH":"$BRANCH" git checkout "$BRANCH" - if ! git cherry-pick -x "$MAIN_SHA"; then - echo "Cherry-pick failed. Please resolve manually on $BRANCH." >&2 - exit 1 + if git merge-base --is-ancestor "$MAIN_SHA" "$BRANCH"; then + echo "Commit $MAIN_SHA already present in $BRANCH. Skipping cherry-pick." + else + if ! git cherry-pick -x "$MAIN_SHA"; then + echo "Cherry-pick failed. Please resolve manually on $BRANCH." >&2 + exit 1 + fi fi else echo "Creating new branch: $BRANCH from main@${MAIN_SHA}" From f3db98d75e3e9cc226ecb15bed6be30ddbd7b952 Mon Sep 17 00:00:00 2001 From: kramaranya Date: Sat, 6 Sep 2025 09:27:08 +0100 Subject: [PATCH 18/36] add script to update the version Signed-off-by: kramaranya --- Makefile | 5 ++--- scripts/set-version.py | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 scripts/set-version.py diff --git a/Makefile b/Makefile index 7617d085..4a7ca92e 100644 --- a/Makefile +++ b/Makefile @@ -67,9 +67,8 @@ uv-venv: .PHONY: release release: uv-venv @if [ -z "$(VERSION)" ]; then echo "Usage: make release VERSION=v0.1.0"; exit 1; fi - @VERSION_NUM=$$(echo "$(VERSION)" | sed 's/^v//'); \ - sed -i '' "s/__version__ = \".*\"/__version__ = \"$$VERSION_NUM\"/" kubeflow/__init__.py; \ - uv run python scripts/gen-changelog.py --token=$${GITHUB_TOKEN} --version=$(VERSION) + @uv run python scripts/set-version.py $(VERSION) + @uv run python scripts/gen-changelog.py --token=$${GITHUB_TOKEN} --version=$(VERSION) # make test-python will produce html coverage by default. Run with `make test-python report=xml` to produce xml report. .PHONY: test-python diff --git a/scripts/set-version.py b/scripts/set-version.py new file mode 100644 index 00000000..c81f5fc9 --- /dev/null +++ b/scripts/set-version.py @@ -0,0 +1,21 @@ +import argparse +import pathlib +import re + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("version") + args = parser.parse_args() + + version = args.version.lstrip("v") + + target = pathlib.Path("kubeflow/__init__.py") + content = target.read_text() + new_content = re.sub(r'__version__\s*=\s*"[^"]*"', f'__version__ = "{version}"', content) + if content != new_content: + target.write_text(new_content) + + +if __name__ == "__main__": + main() From 64045db0f215a63b49747dbc8ab64d184e8bb488 Mon Sep 17 00:00:00 2001 From: kramaranya Date: Thu, 11 Sep 2025 09:27:15 +0100 Subject: [PATCH 19/36] Update make release script Signed-off-by: kramaranya --- Makefile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 4a7ca92e..64a29ced 100644 --- a/Makefile +++ b/Makefile @@ -65,9 +65,11 @@ uv-venv: fi .PHONY: release -release: uv-venv +release: install-dev @if [ -z "$(VERSION)" ]; then echo "Usage: make release VERSION=v0.1.0"; exit 1; fi - @uv run python scripts/set-version.py $(VERSION) + @V_NO_V=$${VERSION#v}; \ + sed -i.bak "s/^__version__ = \".*\"/__version__ = \"$$V_NO_V\"/" kubeflow/__init__.py && \ + rm -f kubeflow/__init__.py.bak @uv run python scripts/gen-changelog.py --token=$${GITHUB_TOKEN} --version=$(VERSION) # make test-python will produce html coverage by default. Run with `make test-python report=xml` to produce xml report. From 7dea8e358160f37119cdb44d9250fc11aa2c4ef1 Mon Sep 17 00:00:00 2001 From: kramaranya Date: Thu, 11 Sep 2025 10:03:41 +0100 Subject: [PATCH 20/36] Use make test-python in release CI Signed-off-by: kramaranya --- .github/workflows/release.yml | 4 ++++ .github/workflows/test-python.yaml | 1 - scripts/set-version.py | 21 --------------------- 3 files changed, 4 insertions(+), 22 deletions(-) delete mode 100644 scripts/set-version.py diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e38b1187..4d1f0403 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -68,6 +68,10 @@ jobs: run: | make verify + - name: Run unit tests + run: | + make test-python + - name: Verify version run: | TAG_VERSION="${{ steps.version.outputs.version }}" diff --git a/.github/workflows/test-python.yaml b/.github/workflows/test-python.yaml index c9d552d7..850bb314 100644 --- a/.github/workflows/test-python.yaml +++ b/.github/workflows/test-python.yaml @@ -4,7 +4,6 @@ on: pull_request: push: branches: [main] - workflow_call: concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/scripts/set-version.py b/scripts/set-version.py deleted file mode 100644 index c81f5fc9..00000000 --- a/scripts/set-version.py +++ /dev/null @@ -1,21 +0,0 @@ -import argparse -import pathlib -import re - - -def main() -> None: - parser = argparse.ArgumentParser() - parser.add_argument("version") - args = parser.parse_args() - - version = args.version.lstrip("v") - - target = pathlib.Path("kubeflow/__init__.py") - content = target.read_text() - new_content = re.sub(r'__version__\s*=\s*"[^"]*"', f'__version__ = "{version}"', content) - if content != new_content: - target.write_text(new_content) - - -if __name__ == "__main__": - main() From 482ccb9c34bd5b214065d4738a289bc8d64dec91 Mon Sep 17 00:00:00 2001 From: kramaranya Date: Sat, 13 Sep 2025 23:52:20 +0100 Subject: [PATCH 21/36] Move prepare release CI to one a single workflow Signed-off-by: kramaranya --- .github/workflows/prepare-release.yml | 76 --------------- .github/workflows/release.yml | 128 ++++++++++++++------------ 2 files changed, 71 insertions(+), 133 deletions(-) delete mode 100644 .github/workflows/prepare-release.yml diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml deleted file mode 100644 index 782c806b..00000000 --- a/.github/workflows/prepare-release.yml +++ /dev/null @@ -1,76 +0,0 @@ -name: Prepare Release - -on: - push: - branches: [ main ] - paths: - - 'kubeflow/__init__.py' - workflow_dispatch: {} - -permissions: - contents: write - -jobs: - prepare: - name: Create release branch - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Configure git user - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - - - name: Check version and branch - id: vars - shell: bash - run: | - VERSION=v$(sed -n 's/^__version__ = "\(.*\)"/\1/p' kubeflow/__init__.py) - MAJOR_MINOR=$(echo "$VERSION" | sed 's/^v//' | cut -d. -f1,2) - BRANCH=release-$MAJOR_MINOR - - echo "version=$VERSION" >> $GITHUB_OUTPUT - echo "branch=$BRANCH" >> $GITHUB_OUTPUT - - - name: Check if tag already exists - id: tagcheck - shell: bash - run: | - if git ls-remote --tags origin "${{ steps.vars.outputs.version }}" | grep -q "refs/tags/${{ steps.vars.outputs.version }}"; then - echo "tag_exists=true" >> "$GITHUB_OUTPUT" - else - echo "tag_exists=false" >> "$GITHUB_OUTPUT" - fi - - - name: Ensure release branch exists and contains version bump - if: steps.tagcheck.outputs.tag_exists == 'false' - shell: bash - run: | - set -euo pipefail - VERSION="${{ steps.vars.outputs.version }}" - BRANCH="${{ steps.vars.outputs.branch }}" - - MAIN_SHA="${{ github.sha }}" - - if git ls-remote --heads origin "$BRANCH" | grep -q "$BRANCH"; then - echo "Using existing branch: $BRANCH" - git fetch origin "$BRANCH":"$BRANCH" - git checkout "$BRANCH" - if git merge-base --is-ancestor "$MAIN_SHA" "$BRANCH"; then - echo "Commit $MAIN_SHA already present in $BRANCH. Skipping cherry-pick." - else - if ! git cherry-pick -x "$MAIN_SHA"; then - echo "Cherry-pick failed. Please resolve manually on $BRANCH." >&2 - exit 1 - fi - fi - else - echo "Creating new branch: $BRANCH from main@${MAIN_SHA}" - git checkout -B "$BRANCH" "$MAIN_SHA" - fi - - git push origin "$BRANCH" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4d1f0403..8fca4b61 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,10 +1,10 @@ name: Release on: - workflow_run: - workflows: ["Prepare Release"] - types: - - completed + push: + branches: [ main ] + paths: + - 'kubeflow/__init__.py' workflow_dispatch: {} permissions: @@ -12,37 +12,32 @@ permissions: id-token: write jobs: - guard: - if: ${{ (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') || github.event_name == 'workflow_dispatch' }} + prepare: + name: Prepare release branch runs-on: ubuntu-latest outputs: - proceed: ${{ steps.set.outputs.proceed }} - steps: - - id: set - run: echo "proceed=true" >> $GITHUB_OUTPUT - build: - name: Build Package - needs: [guard] - if: needs.guard.outputs.proceed == 'true' - runs-on: ubuntu-latest - outputs: - version: ${{ steps.version.outputs.version }} - tag: ${{ steps.version.outputs.tag }} - is-prerelease: ${{ steps.version.outputs.is-prerelease }} - + version: ${{ steps.vars.outputs.version }} + branch: ${{ steps.vars.outputs.branch }} + is-prerelease: ${{ steps.vars.outputs.is-prerelease }} steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} - - name: Extract version info - id: version + - name: Configure git user run: | - VERSION=v$(python -c "import kubeflow; print(kubeflow.__version__)" ) - TAG=$VERSION + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + - name: Check version and branch + id: vars + run: | + VERSION=v$(sed -n 's/^__version__ = "\(.*\)"/\1/p' kubeflow/__init__.py) + MAJOR_MINOR=$(echo "$VERSION" | sed 's/^v//' | cut -d. -f1,2) + BRANCH=release-$MAJOR_MINOR + echo "version=$VERSION" >> $GITHUB_OUTPUT - echo "tag=$TAG" >> $GITHUB_OUTPUT + echo "branch=$BRANCH" >> $GITHUB_OUTPUT if [[ "$VERSION" =~ -rc($|\.) ]]; then echo "is-prerelease=true" >> $GITHUB_OUTPUT @@ -50,12 +45,43 @@ jobs: echo "is-prerelease=false" >> $GITHUB_OUTPUT fi + - name: Ensure release branch exists and contains version bump + run: | + set -euo pipefail + VERSION="${{ steps.vars.outputs.version }}" + BRANCH="${{ steps.vars.outputs.branch }}" + MAIN_SHA="${{ github.sha }}" + + if git ls-remote --heads origin "$BRANCH" | grep -q "$BRANCH"; then + echo "Using existing branch: $BRANCH" + git fetch origin "$BRANCH":"$BRANCH" + git checkout "$BRANCH" + if git merge-base --is-ancestor "$MAIN_SHA" "$BRANCH"; then + echo "Commit $MAIN_SHA already present in $BRANCH. Skipping cherry-pick." + else + if ! git cherry-pick -x "$MAIN_SHA"; then + echo "Cherry-pick failed. Please resolve manually on $BRANCH." >&2 + exit 1 + fi + fi + else + echo "Creating new branch: $BRANCH from main@$MAIN_SHA" + git checkout -B "$BRANCH" "$MAIN_SHA" + fi + git push origin "$BRANCH" + + build: + name: Build package + needs: [prepare] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Checkout release branch run: | - VERSION="${{ steps.version.outputs.version }}" - MAJOR_MINOR=$(echo "$VERSION" | sed 's/^v//' | cut -d. -f1,2) - BRANCH="release-$MAJOR_MINOR" - echo "Switching to $BRANCH" + BRANCH="${{ needs.prepare.outputs.branch }}" git fetch origin "$BRANCH":"$BRANCH" || true git checkout "$BRANCH" @@ -74,17 +100,12 @@ jobs: - name: Verify version run: | - TAG_VERSION="${{ steps.version.outputs.version }}" + TAG_VERSION="${{ needs.prepare.outputs.version }}" CODE_VERSION="v$(python -c "import kubeflow; print(kubeflow.__version__)")" - echo "Tag version: $TAG_VERSION" echo "Code version: $CODE_VERSION" - if [[ "$TAG_VERSION" != "$CODE_VERSION" ]]; then - echo "Version mismatch" - exit 1 - fi - + echo "Version mismatch"; exit 1; fi echo "Version verified: $TAG_VERSION" - name: Build package @@ -98,12 +119,12 @@ jobs: - name: Upload build artifacts uses: actions/upload-artifact@v4 with: - name: dist-${{ steps.version.outputs.version }} + name: dist-${{ needs.prepare.outputs.version }} path: dist/ create-tag: - name: Create and Push Tag - needs: [build] + name: Create and push tag + needs: [prepare, build] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -111,11 +132,10 @@ jobs: fetch-depth: 0 - name: Create tag run: | - VERSION="${{ needs.build.outputs.version }}" + VERSION="${{ needs.prepare.outputs.version }}" + BRANCH="${{ needs.prepare.outputs.branch }}" if git ls-remote --tags origin "$VERSION" | grep -q "refs/tags/$VERSION"; then echo "Tag $VERSION already exists. Skipping"; exit 0; fi - MAJOR_MINOR=$(echo "$VERSION" | sed 's/^v//' | cut -d. -f1,2) - BRANCH="release-$MAJOR_MINOR" git fetch origin "$BRANCH":"$BRANCH" git checkout "$BRANCH" git tag "$VERSION" @@ -123,19 +143,17 @@ jobs: publish-pypi: name: Publish to PyPI - needs: [build, create-tag] + needs: [prepare, build, create-tag] runs-on: ubuntu-latest environment: name: release url: https://pypi.org/project/kubeflow/ - steps: - name: Download build artifacts uses: actions/download-artifact@v4 with: - name: dist-${{ needs.build.outputs.version }} + name: dist-${{ needs.prepare.outputs.version }} path: dist/ - - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: @@ -143,24 +161,21 @@ jobs: github-release: name: Create GitHub Release - needs: [build, create-tag, publish-pypi] + needs: [prepare, build, create-tag, publish-pypi] runs-on: ubuntu-latest environment: name: release url: https://github.com/kubeflow/sdk/releases - steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Extract changelog id: changelog run: | - VERSION="${{ needs.build.outputs.version }}" + VERSION="${{ needs.prepare.outputs.version }}" MAJOR_MINOR=$(echo "$VERSION" | sed 's/^v//' | cut -d. -f1,2) CHANGELOG_FILE="CHANGELOG/CHANGELOG-${MAJOR_MINOR}.md" - if [[ -f "$CHANGELOG_FILE" ]]; then set -euo pipefail HEADER_REGEX="^# \\[${VERSION//./\\.}\\]" @@ -183,14 +198,13 @@ jobs: else echo "changelog=**Note:** $CHANGELOG_FILE not found" >> $GITHUB_OUTPUT fi - - name: Create GitHub Release uses: softprops/action-gh-release@v1 with: - tag_name: ${{ needs.build.outputs.tag }} - name: Kubeflow SDK ${{ needs.build.outputs.tag }} + tag_name: ${{ needs.prepare.outputs.version }} + name: Kubeflow SDK ${{ needs.prepare.outputs.version }} body: | ${{ steps.changelog.outputs.changelog }} draft: false - prerelease: ${{ needs.build.outputs.is-prerelease == 'true' }} - generate_release_notes: true + prerelease: ${{ needs.prepare.outputs.is-prerelease == 'true' }} + generate_release_notes: false From 0e200d9ce1fb0097ada7a7e3b04c7fbcb40c03c5 Mon Sep 17 00:00:00 2001 From: kramaranya Date: Sun, 14 Sep 2025 18:57:53 +0100 Subject: [PATCH 22/36] Remove uv.sync from RELEASE.md Signed-off-by: kramaranya --- docs/release/README.md => RELEASE.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) rename docs/release/README.md => RELEASE.md (97%) diff --git a/docs/release/README.md b/RELEASE.md similarity index 97% rename from docs/release/README.md rename to RELEASE.md index 4c942e7f..effe7ae8 100644 --- a/docs/release/README.md +++ b/RELEASE.md @@ -7,12 +7,6 @@ - Create a [GitHub Token](https://docs.github.com/en/github/authenticating-to-github/keeping-your-account-and-data-secure/creating-a-personal-access-token) and set it as `GITHUB_TOKEN` environment variable. -- Install development dependencies: - - ```bash - uv sync - ``` - ## Versioning Policy Kubeflow SDK version format follows [Semantic Versioning](https://semver.org/). @@ -63,7 +57,7 @@ The Kubeflow SDK uses an automated release process with GitHub Actions: #### 1. Update Version and Changelog -1. Generate version and changelog locally: +1. Generate version and changelog locally (this will sync dependencies automatically): ```sh export GITHUB_TOKEN= From 8f56800434ca7d6d8738fcf07ff77abd4177238e Mon Sep 17 00:00:00 2001 From: kramaranya Date: Sun, 14 Sep 2025 22:46:58 +0100 Subject: [PATCH 23/36] Upload artifacts to GitHub Release Signed-off-by: kramaranya --- .github/workflows/release.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8fca4b61..b329fa2b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -170,6 +170,11 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: dist-${{ needs.prepare.outputs.version }} + path: dist/ - name: Extract changelog id: changelog run: | @@ -208,3 +213,5 @@ jobs: draft: false prerelease: ${{ needs.prepare.outputs.is-prerelease == 'true' }} generate_release_notes: false + files: | + dist/* From 73021944904f3072837d7c6382908dfab88a503e Mon Sep 17 00:00:00 2001 From: kramaranya Date: Mon, 15 Sep 2025 07:02:34 +0100 Subject: [PATCH 24/36] Run release CI against release-* branch Signed-off-by: kramaranya --- .github/workflows/release.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b329fa2b..6a52f7c6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,7 +2,9 @@ name: Release on: push: - branches: [ main ] + branches: + - main + - 'release-*' paths: - 'kubeflow/__init__.py' workflow_dispatch: {} @@ -14,6 +16,7 @@ permissions: jobs: prepare: name: Prepare release branch + if: ${{ !(github.event_name == 'push' && startsWith(github.ref, 'refs/heads/release-') && github.actor == 'github-actions[bot]') }} runs-on: ubuntu-latest outputs: version: ${{ steps.vars.outputs.version }} @@ -52,6 +55,11 @@ jobs: BRANCH="${{ steps.vars.outputs.branch }}" MAIN_SHA="${{ github.sha }}" + if [[ "${GITHUB_REF_NAME}" == "$BRANCH" ]]; then + echo "Triggered on $BRANCH. Skipping cherry-pick from main." + exit 0 + fi + if git ls-remote --heads origin "$BRANCH" | grep -q "$BRANCH"; then echo "Using existing branch: $BRANCH" git fetch origin "$BRANCH":"$BRANCH" From 366af93c5c8704c4b6186253fc3ce8edfa3993d9 Mon Sep 17 00:00:00 2001 From: kramaranya Date: Mon, 15 Sep 2025 07:17:00 +0100 Subject: [PATCH 25/36] Use X.Y.Z versioning in Kubeflow SDK Signed-off-by: kramaranya --- .github/workflows/release.yml | 14 +++++++------- Makefile | 4 ++-- RELEASE.md | 30 +++++++++++++++--------------- scripts/gen-changelog.py | 4 ++-- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6a52f7c6..92129b1c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,14 +35,14 @@ jobs: - name: Check version and branch id: vars run: | - VERSION=v$(sed -n 's/^__version__ = "\(.*\)"/\1/p' kubeflow/__init__.py) - MAJOR_MINOR=$(echo "$VERSION" | sed 's/^v//' | cut -d. -f1,2) + VERSION=$(sed -n 's/^__version__ = "\(.*\)"/\1/p' kubeflow/__init__.py) + MAJOR_MINOR=$(echo "$VERSION" | cut -d. -f1,2) BRANCH=release-$MAJOR_MINOR echo "version=$VERSION" >> $GITHUB_OUTPUT echo "branch=$BRANCH" >> $GITHUB_OUTPUT - if [[ "$VERSION" =~ -rc($|\.) ]]; then + if [[ "$VERSION" =~ rc[0-9]+$ ]]; then echo "is-prerelease=true" >> $GITHUB_OUTPUT else echo "is-prerelease=false" >> $GITHUB_OUTPUT @@ -109,7 +109,7 @@ jobs: - name: Verify version run: | TAG_VERSION="${{ needs.prepare.outputs.version }}" - CODE_VERSION="v$(python -c "import kubeflow; print(kubeflow.__version__)")" + CODE_VERSION="$(python -c "import kubeflow; print(kubeflow.__version__)")" echo "Tag version: $TAG_VERSION" echo "Code version: $CODE_VERSION" if [[ "$TAG_VERSION" != "$CODE_VERSION" ]]; then @@ -187,15 +187,15 @@ jobs: id: changelog run: | VERSION="${{ needs.prepare.outputs.version }}" - MAJOR_MINOR=$(echo "$VERSION" | sed 's/^v//' | cut -d. -f1,2) + MAJOR_MINOR=$(echo "$VERSION" | cut -d. -f1,2) CHANGELOG_FILE="CHANGELOG/CHANGELOG-${MAJOR_MINOR}.md" if [[ -f "$CHANGELOG_FILE" ]]; then set -euo pipefail HEADER_REGEX="^# \\[${VERSION//./\\.}\\]" SECTION=$(sed -n "/$HEADER_REGEX/,\$p" "$CHANGELOG_FILE" | tail -n +2) - NEXT_VERSION=$(echo "$SECTION" | grep -m1 "^# \\[v[0-9]" || true) + NEXT_VERSION=$(echo "$SECTION" | grep -m1 "^# \\[[0-9]" || true) if [[ -n "$NEXT_VERSION" ]]; then - CHANGELOG=$(echo "$SECTION" | sed -n "1,/^# \\[v[0-9]/p" | sed '1d;$d') + CHANGELOG=$(echo "$SECTION" | sed -n "1,/^# \\[[0-9]/p" | sed '1d;$d') else CHANGELOG=$(echo "$SECTION" | sed '1d') fi diff --git a/Makefile b/Makefile index 64a29ced..29d3b059 100644 --- a/Makefile +++ b/Makefile @@ -66,8 +66,8 @@ uv-venv: .PHONY: release release: install-dev - @if [ -z "$(VERSION)" ]; then echo "Usage: make release VERSION=v0.1.0"; exit 1; fi - @V_NO_V=$${VERSION#v}; \ + @if [ -z "$(VERSION)" ]; then echo "Usage: make release VERSION=0.1.0"; exit 1; fi + @V_NO_V=$(VERSION); \ sed -i.bak "s/^__version__ = \".*\"/__version__ = \"$$V_NO_V\"/" kubeflow/__init__.py && \ rm -f kubeflow/__init__.py.bak @uv run python scripts/gen-changelog.py --token=$${GITHUB_TOKEN} --version=$(VERSION) diff --git a/RELEASE.md b/RELEASE.md index effe7ae8..92f88276 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -9,23 +9,23 @@ ## Versioning Policy -Kubeflow SDK version format follows [Semantic Versioning](https://semver.org/). -Kubeflow SDK versions are in the format of `vX.Y.Z`, where `X` is the major version, `Y` is +Kubeflow SDK version format follows Python's [PEP 440](https://peps.python.org/pep-0440/). +Kubeflow SDK versions are in the format of `X.Y.Z`, where `X` is the major version, `Y` is the minor version, and `Z` is the patch version. The patch version contains only bug fixes. -Additionally, Kubeflow SDK does pre-releases in this format: `vX.Y.Z-rc.N` where `N` is a number -of the `Nth` release candidate (RC) before an upcoming public release named `vX.Y.Z`. +Additionally, Kubeflow SDK does pre-releases in this format: `X.Y.ZrcN` where `N` is a number +of the `Nth` release candidate (RC) before an upcoming public release named `X.Y.Z`. ## Release Branches and Tags -Kubeflow SDK releases are tagged with tags like `vX.Y.Z`, for example `v0.1.0`. +Kubeflow SDK releases are tagged with tags like `X.Y.Z`, for example `0.1.0`. Release branches are in the format of `release-X.Y`, where `X.Y` stands for the minor release. -`vX.Y.Z` releases are released from the `release-X.Y` branch. For example, -`v0.1.0` release should be on `release-0.1` branch. +`X.Y.Z` releases are released from the `release-X.Y` branch. For example, +`0.1.0` release should be on `release-0.1` branch. If you want to push changes to the `release-X.Y` release branch, you have to cherry pick your changes from the `main` branch and submit a PR. @@ -61,12 +61,12 @@ The Kubeflow SDK uses an automated release process with GitHub Actions: ```sh export GITHUB_TOKEN= - make release VERSION=vX.Y.Z + make release VERSION=X.Y.Z ``` This updates: - `kubeflow/__init__.py` with `__version__ = "X.Y.Z"` -- `CHANGELOG/CHANGELOG-X.Y.md` with a new top entry `# [vX.Y.Z] (YYYY-MM-DD)` +- `CHANGELOG/CHANGELOG-X.Y.md` with a new top entry `# [X.Y.Z] (YYYY-MM-DD)` 2. Open a PR: - Review `kubeflow/__init__.py` and `CHANGELOG/CHANGELOG-X.Y.md` @@ -107,23 +107,23 @@ The `Release` GitHub Action automatically: ### Release Types -#### Major/Minor Release (vX.Y.0) +#### Major/Minor Release (X.Y.0) - Creates new `release-X.Y` branch - Uses full changelog since previous minor version -- Example: `make release VERSION=v0.2.0` +- Example: `make release VERSION=0.2.0` -#### Patch Release (vX.Y.Z) +#### Patch Release (X.Y.Z) - Updates existing `release-X.Y` branch - Cherry-pick fixes from main to release branch -- Example: `make release VERSION=v0.2.1` +- Example: `make release VERSION=0.2.1` -#### Release Candidate (vX.Y.Z-rc.N) +#### Release Candidate (X.Y.ZrcN) - Creates pre-release - GitHub Release marked as "pre-release" -- Example: `make release VERSION=v0.2.0-rc.1` +- Example: `make release VERSION=0.2.0rc1` ## Announcement diff --git a/scripts/gen-changelog.py b/scripts/gen-changelog.py index cd7452e4..54000728 100755 --- a/scripts/gen-changelog.py +++ b/scripts/gen-changelog.py @@ -34,7 +34,7 @@ def get_initial_commit(github_repo): def main(): parser = argparse.ArgumentParser(description="Generate changelog for Kubeflow SDK") parser.add_argument("--token", required=True, help="GitHub Access Token") - parser.add_argument("--version", required=True, help="Target version (e.g. v0.1.0)") + parser.add_argument("--version", required=True, help="Target version (e.g. 0.1.0)") args = parser.parse_args() @@ -87,7 +87,7 @@ def main(): release_date = str(commits[-1].commit.author.date).split(" ")[0] release_url = f"https://github.com/{REPO_NAME}/releases/tag/{current_release}" - major_minor_parts = current_release.lstrip('v').split('.')[:2] + major_minor_parts = current_release.split('.')[:2] major_minor = '.'.join(major_minor_parts) changelog_file = os.path.join(CHANGELOG_DIR, f"CHANGELOG-{major_minor}.md") From 8199dd193089022b67caafb0cfaac0fb1e81d226 Mon Sep 17 00:00:00 2001 From: kramaranya Date: Mon, 15 Sep 2025 07:59:41 +0100 Subject: [PATCH 26/36] Use single workflow for release Signed-off-by: kramaranya --- RELEASE.md | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/RELEASE.md b/RELEASE.md index 92f88276..b62de371 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -72,34 +72,25 @@ This updates: - Review `kubeflow/__init__.py` and `CHANGELOG/CHANGELOG-X.Y.md` - Open a PR to `main` and get it reviewed and merged -#### 2. Automated Release Branch Creation - -The `Prepare Release` GitHub Action automatically: - -- Detects the version change in `kubeflow/__init__.py` -- Creates or updates the `release-X.Y` branch -- Cherry-picks the version bump commit from main - -**Verification**: Confirm the release branch was created/updated! - -#### 3. Automated Release Process +#### 2. Automated Release Process The `Release` GitHub Action automatically: -- Runs tests and builds the package -- Creates and pushes the release tag -- Publishes to PyPI (requires manual approval) -- Creates GitHub Release (requires manual approval) +1. **Prepare**: Detects the version change in `kubeflow/__init__.py` and creates or updates the `release-X.Y` branch +2. **Build**: Runs tests and builds the package on the release branch +3. **Tag**: Creates and pushes the release tag +4. **Publish**: Publishes to PyPI (requires manual approval) +5. **Release**: Creates GitHub Release (requires manual approval) -**Verification**: Confirm the release tag was created! +**Verification**: Confirm the release branch and tag were created! -#### 4. Manual Approvals +#### 3. Manual Approvals 1. **PyPI Publishing**: Go to [GitHub Actions](https://github.com/kubeflow/sdk/actions) → `Release` workflow → Approve "Publish to PyPI" 2. **GitHub Release**: After PyPI approval → Approve "Create GitHub Release" -#### 5. Final Verification +#### 4. Final Verification 1. Verify the release on [PyPI](https://pypi.org/project/kubeflow/) 2. Verify the release on [GitHub Releases](https://github.com/kubeflow/sdk/releases) From d6d5974ebd34ec18c18b1b46041b523895c73980 Mon Sep 17 00:00:00 2001 From: kramaranya Date: Mon, 15 Sep 2025 08:13:14 +0100 Subject: [PATCH 27/36] Remove Release types from RELEASE.md Signed-off-by: kramaranya --- RELEASE.md | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/RELEASE.md b/RELEASE.md index b62de371..633374c9 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -96,25 +96,6 @@ The `Release` GitHub Action automatically: 2. Verify the release on [GitHub Releases](https://github.com/kubeflow/sdk/releases) 3. Test installation: `pip install kubeflow==X.Y.Z` -### Release Types - -#### Major/Minor Release (X.Y.0) - -- Creates new `release-X.Y` branch -- Uses full changelog since previous minor version -- Example: `make release VERSION=0.2.0` - -#### Patch Release (X.Y.Z) - -- Updates existing `release-X.Y` branch -- Cherry-pick fixes from main to release branch -- Example: `make release VERSION=0.2.1` - -#### Release Candidate (X.Y.ZrcN) - -- Creates pre-release -- GitHub Release marked as "pre-release" -- Example: `make release VERSION=0.2.0rc1` ## Announcement From cb17274fa0de2ed1bd3863bff345c3e2167d33e2 Mon Sep 17 00:00:00 2001 From: kramaranya Date: Mon, 15 Sep 2025 08:17:58 +0100 Subject: [PATCH 28/36] Add a note about older minor series patch release Signed-off-by: kramaranya --- RELEASE.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/RELEASE.md b/RELEASE.md index 633374c9..a495f1eb 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -70,7 +70,8 @@ This updates: 2. Open a PR: - Review `kubeflow/__init__.py` and `CHANGELOG/CHANGELOG-X.Y.md` - - Open a PR to `main` and get it reviewed and merged + - **For latest minor series**: Open a PR to `main` and get it reviewed and merged + - **For older minor series patch (e.g. 0.1.1 when main is at 0.2.x)**: Open a PR to the corresponding `release-X.Y` branch #### 2. Automated Release Process From 22d8cc877460b9076fb12c340d2f4bec7a7da9cc Mon Sep 17 00:00:00 2001 From: kramaranya Date: Mon, 15 Sep 2025 13:28:02 +0100 Subject: [PATCH 29/36] Directly checkout release branch with ref input Signed-off-by: kramaranya --- .github/workflows/release.yml | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 92129b1c..5f6f2b31 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -86,12 +86,7 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 - - - name: Checkout release branch - run: | - BRANCH="${{ needs.prepare.outputs.branch }}" - git fetch origin "$BRANCH":"$BRANCH" || true - git checkout "$BRANCH" + ref: ${{ needs.prepare.outputs.branch }} - name: Set up Python uses: actions/setup-python@v5 @@ -116,12 +111,9 @@ jobs: echo "Version mismatch"; exit 1; fi echo "Version verified: $TAG_VERSION" - - name: Build package + - name: Build and validate package run: | uv build - - - name: Validate package - run: | uvx twine check dist/* - name: Upload build artifacts @@ -138,14 +130,12 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 + ref: ${{ needs.prepare.outputs.branch }} - name: Create tag run: | VERSION="${{ needs.prepare.outputs.version }}" - BRANCH="${{ needs.prepare.outputs.branch }}" if git ls-remote --tags origin "$VERSION" | grep -q "refs/tags/$VERSION"; then echo "Tag $VERSION already exists. Skipping"; exit 0; fi - git fetch origin "$BRANCH":"$BRANCH" - git checkout "$BRANCH" git tag "$VERSION" git push origin "$VERSION" From 98b1a07a5275ef50ee150d9340f21e8feb610816 Mon Sep 17 00:00:00 2001 From: kramaranya Date: Mon, 15 Sep 2025 13:51:44 +0100 Subject: [PATCH 30/36] Checkout release branch with ref input Signed-off-by: kramaranya --- .github/workflows/release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5f6f2b31..8e20f282 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -168,6 +168,7 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 + ref: ${{ needs.prepare.outputs.branch }} - name: Download build artifacts uses: actions/download-artifact@v4 with: From 04e4b9b1329ef5972328ec27fedea93b4bfb7fc9 Mon Sep 17 00:00:00 2001 From: kramaranya Date: Mon, 15 Sep 2025 15:01:30 +0100 Subject: [PATCH 31/36] Update extract changelog job Signed-off-by: kramaranya --- .github/workflows/release.yml | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8e20f282..ecbba671 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -180,28 +180,23 @@ jobs: VERSION="${{ needs.prepare.outputs.version }}" MAJOR_MINOR=$(echo "$VERSION" | cut -d. -f1,2) CHANGELOG_FILE="CHANGELOG/CHANGELOG-${MAJOR_MINOR}.md" - if [[ -f "$CHANGELOG_FILE" ]]; then - set -euo pipefail - HEADER_REGEX="^# \\[${VERSION//./\\.}\\]" - SECTION=$(sed -n "/$HEADER_REGEX/,\$p" "$CHANGELOG_FILE" | tail -n +2) - NEXT_VERSION=$(echo "$SECTION" | grep -m1 "^# \\[[0-9]" || true) - if [[ -n "$NEXT_VERSION" ]]; then - CHANGELOG=$(echo "$SECTION" | sed -n "1,/^# \\[[0-9]/p" | sed '1d;$d') - else - CHANGELOG=$(echo "$SECTION" | sed '1d') - fi - if [[ -n "$CHANGELOG" ]]; then - { - echo "changelog<> $GITHUB_OUTPUT - else - echo "changelog=**Note:** No changelog section found for $VERSION in $CHANGELOG_FILE" >> $GITHUB_OUTPUT - fi + set -euo pipefail + [[ -f "$CHANGELOG_FILE" ]] || { echo "ERROR: $CHANGELOG_FILE not found" >&2; exit 1; } + HEADER_REGEX="^# \\[${VERSION//./\\.}\\]" + SECTION=$(sed -n "/$HEADER_REGEX/,\$p" "$CHANGELOG_FILE" | tail -n +2) + [[ -n "$SECTION" ]] || { echo "ERROR: No changelog section for $VERSION in $CHANGELOG_FILE" >&2; exit 1; } + NEXT_VERSION=$(echo "$SECTION" | grep -m1 "^# \\[[0-9]" || true) + if [[ -n "$NEXT_VERSION" ]]; then + CHANGELOG=$(echo "$SECTION" | sed -n "1,/^# \\[[0-9]/p" | sed '1d;$d') else - echo "changelog=**Note:** $CHANGELOG_FILE not found" >> $GITHUB_OUTPUT + CHANGELOG=$(echo "$SECTION" | sed '1d') fi + [[ -n "$CHANGELOG" ]] || { echo "ERROR: Empty changelog body for $VERSION in $CHANGELOG_FILE" >&2; exit 1; } + { + echo "changelog<> $GITHUB_OUTPUT - name: Create GitHub Release uses: softprops/action-gh-release@v1 with: From 7a98c0cb809bae33284da67e0d1235cd779c393a Mon Sep 17 00:00:00 2001 From: kramaranya Date: Mon, 15 Sep 2025 15:05:01 +0100 Subject: [PATCH 32/36] Update the name of github release Signed-off-by: kramaranya --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ecbba671..e25d8502 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -201,7 +201,7 @@ jobs: uses: softprops/action-gh-release@v1 with: tag_name: ${{ needs.prepare.outputs.version }} - name: Kubeflow SDK ${{ needs.prepare.outputs.version }} + name: ${{ needs.prepare.outputs.version }} body: | ${{ steps.changelog.outputs.changelog }} draft: false From 2057a66d79d0ff44276335ef45a1678992d8bad9 Mon Sep 17 00:00:00 2001 From: kramaranya Date: Mon, 15 Sep 2025 16:10:33 +0100 Subject: [PATCH 33/36] Remove full changelog line Signed-off-by: kramaranya --- scripts/gen-changelog.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/scripts/gen-changelog.py b/scripts/gen-changelog.py index 54000728..08dd54cf 100755 --- a/scripts/gen-changelog.py +++ b/scripts/gen-changelog.py @@ -117,8 +117,6 @@ def main(): changelog_content.append("## Other Changes\n\n") changelog_content.append("\n".join(categories['misc']) + "\n\n") - changelog_content.append(f"**Full Changelog**: {comparison.html_url}\n\n") - try: with open(changelog_file, "r") as f: existing_content = f.read() From 74de8ca7eca490b16c02494d260777e05c13fe66 Mon Sep 17 00:00:00 2001 From: kramaranya Date: Mon, 15 Sep 2025 16:14:39 +0100 Subject: [PATCH 34/36] Fix ruff issue Signed-off-by: kramaranya --- scripts/gen-changelog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/gen-changelog.py b/scripts/gen-changelog.py index 08dd54cf..df687ee8 100755 --- a/scripts/gen-changelog.py +++ b/scripts/gen-changelog.py @@ -118,7 +118,7 @@ def main(): changelog_content.append("\n".join(categories['misc']) + "\n\n") try: - with open(changelog_file, "r") as f: + with open(changelog_file) as f: existing_content = f.read() except FileNotFoundError: existing_content = "" From ed4363dd6be28ede03ae3c6f9f360ffc978c45f7 Mon Sep 17 00:00:00 2001 From: kramaranya Date: Mon, 15 Sep 2025 16:29:34 +0100 Subject: [PATCH 35/36] Don't use link for authors in changelog Signed-off-by: kramaranya --- scripts/gen-changelog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/gen-changelog.py b/scripts/gen-changelog.py index df687ee8..e3184300 100755 --- a/scripts/gen-changelog.py +++ b/scripts/gen-changelog.py @@ -77,7 +77,7 @@ def main(): category = categorize_pr(pr.title) pr_entry = (f"- {pr.title} ([#{pr.number}]({pr.html_url})) " - f"by [@{pr.user.login}]({pr.user.html_url})") + f"by @{pr.user.login}") categories[category].append(pr_entry) if not pr_set: From 290b2a48ec6cabfa9d6ad057e2aff9597776c518 Mon Sep 17 00:00:00 2001 From: kramaranya Date: Wed, 17 Sep 2025 00:30:00 +0100 Subject: [PATCH 36/36] Remove chnagelog for RCs Signed-off-by: kramaranya --- .github/workflows/release.yml | 4 ++-- scripts/gen-changelog.py | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e25d8502..5a079137 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -175,6 +175,7 @@ jobs: name: dist-${{ needs.prepare.outputs.version }} path: dist/ - name: Extract changelog + if: needs.prepare.outputs.is-prerelease != 'true' id: changelog run: | VERSION="${{ needs.prepare.outputs.version }}" @@ -202,8 +203,7 @@ jobs: with: tag_name: ${{ needs.prepare.outputs.version }} name: ${{ needs.prepare.outputs.version }} - body: | - ${{ steps.changelog.outputs.changelog }} + body: ${{ steps.changelog.outputs.changelog }} draft: false prerelease: ${{ needs.prepare.outputs.is-prerelease == 'true' }} generate_release_notes: false diff --git a/scripts/gen-changelog.py b/scripts/gen-changelog.py index e3184300..ca5c96af 100755 --- a/scripts/gen-changelog.py +++ b/scripts/gen-changelog.py @@ -1,5 +1,6 @@ import argparse import os +import re import sys try: @@ -39,6 +40,10 @@ def main(): args = parser.parse_args() current_release = args.version + + if re.search(r"rc\d+$", current_release): + print("Skipping changelog generation for pre release") + return github_repo = Github(args.token).get_repo(REPO_NAME) try: