From d7f37f9f9c2bcfe507f2d2a51f2fc19f815cf9e6 Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Tue, 19 May 2026 04:44:48 -0700 Subject: [PATCH 1/4] ci(sdk): replace release-plz with tag-driven idempotent publish MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The release-plz setup hit a wall: the Dstack-TEE org disallows GitHub Actions from creating pull requests, which is the central mechanism release-plz relies on. Plus, the SDK release cadence is low and only involves two coupled crates, so PR-driven automation is overkill. Replace with a simple, explicit workflow: - Trigger: push of a tag matching `dstack-sdk-v*` (single tag covers both crates, which share a version). - Validate that all three version sources agree with the tag: - sdk/rust/types/Cargo.toml [package.version] - sdk/rust/Cargo.toml [package.version] - root Cargo.toml [workspace.dependencies.dstack-sdk-types.version] Fail fast with a clear message if they drift — avoiding the "tag rust-sdk-v0.5.9 but crate is 0.1.2" failure mode that broke the previous workflow. - Publish idempotently via .github/scripts/cargo-publish-idempotent.sh: "already exists on crates.io" is treated as success, so a partial failure can be retried by pushing the same tag without getting stuck on the first crate. - Auto-create a matching GitHub Release for visibility. Drop release-plz.toml. Release workflow for humans: cargo set-version -p dstack-sdk-types -p dstack-sdk # also bump root Cargo.toml workspace.dependencies.dstack-sdk-types.version git commit -am "release: dstack-sdk " git tag dstack-sdk-v git push origin master "dstack-sdk-v" --- .github/scripts/cargo-publish-idempotent.sh | 26 +++++ .github/workflows/rust-sdk-release.yml | 119 +++++++++++++------- release-plz.toml | 21 ---- 3 files changed, 105 insertions(+), 61 deletions(-) create mode 100755 .github/scripts/cargo-publish-idempotent.sh delete mode 100644 release-plz.toml diff --git a/.github/scripts/cargo-publish-idempotent.sh b/.github/scripts/cargo-publish-idempotent.sh new file mode 100755 index 00000000..5521b014 --- /dev/null +++ b/.github/scripts/cargo-publish-idempotent.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: © 2026 Phala Network +# +# SPDX-License-Identifier: Apache-2.0 +# +# Wraps `cargo publish -p $1` so that "already exists on crates.io" is treated +# as success. Lets a partially-failed release be retried by pushing the same +# tag, without getting stuck on the first crate. + +set -euo pipefail + +crate=${1:?missing crate name} + +if output=$(cargo publish -p "$crate" 2>&1); then + echo "$output" + exit 0 +fi + +echo "$output" + +if grep -q "already exists on crates.io index" <<<"$output"; then + echo "::notice::$crate is already published at this version; treating as success" + exit 0 +fi + +exit 1 diff --git a/.github/workflows/rust-sdk-release.yml b/.github/workflows/rust-sdk-release.yml index 586e3838..db05fcd2 100644 --- a/.github/workflows/rust-sdk-release.yml +++ b/.github/workflows/rust-sdk-release.yml @@ -2,55 +2,94 @@ # # SPDX-License-Identifier: Apache-2.0 -name: Release-plz +name: Publish SDK to crates.io on: push: - branches: - - master + tags: ['dstack-sdk-v*'] jobs: - release-plz-pr: - name: Release-plz PR + publish: runs-on: ubuntu-latest - if: ${{ github.repository_owner == 'Dstack-TEE' }} - permissions: - contents: write - pull-requests: write - concurrency: - group: release-plz-${{ github.ref }} - cancel-in-progress: false - steps: - - uses: actions/checkout@v5 - with: - fetch-depth: 0 - persist-credentials: false - - uses: dtolnay/rust-toolchain@stable - - name: Run release-plz - uses: release-plz/action@v0.5 - with: - command: release-pr - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - release-plz-release: - name: Release-plz release - runs-on: ubuntu-latest - if: ${{ github.repository_owner == 'Dstack-TEE' }} environment: sdk-release permissions: - contents: write - pull-requests: read id-token: write + contents: read steps: - uses: actions/checkout@v5 - with: - fetch-depth: 0 - persist-credentials: false - - uses: dtolnay/rust-toolchain@stable - - name: Run release-plz - uses: release-plz/action@v0.5 - with: - command: release + + - name: Extract version from tag + id: ver + run: | + tag="${GITHUB_REF_NAME}" + version="${tag#dstack-sdk-v}" + if [[ -z "$version" || "$version" == "$tag" ]]; then + echo "::error::tag '$tag' does not start with 'dstack-sdk-v'" + exit 1 + fi + echo "version=$version" >> "$GITHUB_OUTPUT" + echo "Publishing version: $version" + + - name: Verify Cargo.toml versions match tag + env: + VERSION: ${{ steps.ver.outputs.version }} + run: | + python3 <<'PY' + import os, sys, tomllib + + want = os.environ["VERSION"] + + def pkg_version(path): + with open(path, "rb") as f: + return tomllib.load(f)["package"]["version"] + + def ws_dep_version(path, name): + with open(path, "rb") as f: + dep = tomllib.load(f)["workspace"]["dependencies"][name] + return dep["version"] if isinstance(dep, dict) else dep + + checks = [ + ("sdk/rust/types/Cargo.toml [package.version]", + pkg_version("sdk/rust/types/Cargo.toml")), + ("sdk/rust/Cargo.toml [package.version]", + pkg_version("sdk/rust/Cargo.toml")), + ("Cargo.toml [workspace.dependencies.dstack-sdk-types.version]", + ws_dep_version("Cargo.toml", "dstack-sdk-types")), + ] + + fail = False + for label, got in checks: + ok = got == want + fail = fail or not ok + print(f" {'OK ' if ok else 'BAD'} {label}: {got}") + + if fail: + print(f"\ntag is dstack-sdk-v{want}; bump all three to {want} before tagging.", + file=sys.stderr) + sys.exit(1) + PY + + - uses: rust-lang/crates-io-auth-action@v1 + id: auth + + - name: Publish dstack-sdk-types + env: + CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }} + VERSION: ${{ steps.ver.outputs.version }} + run: .github/scripts/cargo-publish-idempotent.sh dstack-sdk-types + + - name: Publish dstack-sdk + env: + CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }} + VERSION: ${{ steps.ver.outputs.version }} + run: .github/scripts/cargo-publish-idempotent.sh dstack-sdk + + - name: Create GitHub Release env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ github.token }} + VERSION: ${{ steps.ver.outputs.version }} + run: | + gh release create "$GITHUB_REF_NAME" \ + --title "dstack-sdk $VERSION" \ + --notes "Published to crates.io: [dstack-sdk@$VERSION](https://crates.io/crates/dstack-sdk/$VERSION), [dstack-sdk-types@$VERSION](https://crates.io/crates/dstack-sdk-types/$VERSION)" \ + --verify-tag diff --git a/release-plz.toml b/release-plz.toml deleted file mode 100644 index 3fc0bd23..00000000 --- a/release-plz.toml +++ /dev/null @@ -1,21 +0,0 @@ -# SPDX-FileCopyrightText: © 2026 Phala Network -# -# SPDX-License-Identifier: Apache-2.0 - -# release-plz only manages the public SDK crates. Every other workspace -# member is internal and must never be published to crates.io. - -[workspace] -release = false -pr_branch_prefix = "release-plz/" -pr_labels = ["release"] -semver_check = true -changelog_update = true - -[[package]] -name = "dstack-sdk-types" -release = true - -[[package]] -name = "dstack-sdk" -release = true From 35e20fe94c30b58b910b6bb0816dfd8cf9f8ad5c Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Tue, 19 May 2026 04:58:45 -0700 Subject: [PATCH 2/4] fix(sdk): align workspace dep pin with crate version The workspace dependency pin for dstack-sdk-types was at 0.1.1 while both crates have been at 0.1.2 since release. This pin had no effect on the path-based dependency resolution but blocked crates.io publishing (cargo refuses if the version in workspace.dependencies doesn't exist). --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index d913838c..7c83d291 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,7 +61,7 @@ resolver = "2" # Internal dependencies ra-rpc = { path = "ra-rpc", default-features = false } ra-tls = { path = "ra-tls" } -dstack-sdk-types = { path = "sdk/rust/types", version = "0.1.1", default-features = false } +dstack-sdk-types = { path = "sdk/rust/types", version = "0.1.2", default-features = false } dstack-gateway-rpc = { path = "gateway/rpc" } dstack-kms-rpc = { path = "kms/rpc" } dstack-guest-agent-rpc = { path = "guest-agent/rpc" } From 5d9e847befbea4df8209f292f50f8370d34c6bce Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Tue, 19 May 2026 04:59:49 -0700 Subject: [PATCH 3/4] ci(sdk): grant contents: write for GitHub Release creation --- .github/workflows/rust-sdk-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rust-sdk-release.yml b/.github/workflows/rust-sdk-release.yml index db05fcd2..9111c91d 100644 --- a/.github/workflows/rust-sdk-release.yml +++ b/.github/workflows/rust-sdk-release.yml @@ -14,7 +14,7 @@ jobs: environment: sdk-release permissions: id-token: write - contents: read + contents: write steps: - uses: actions/checkout@v5 From 261f2c657027f488058ab27be423fb58eac07842 Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Tue, 19 May 2026 05:05:33 -0700 Subject: [PATCH 4/4] ci(sdk): make GitHub Release creation idempotent Address Copilot review: previously a successful run would create the GitHub Release, and any retry (push the same tag again) would then fail at this step even though the cargo publish wrapper handles "already exists". Skip the create when a release for this tag is already there, matching the cargo publish wrapper's behavior. --- .github/workflows/rust-sdk-release.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/rust-sdk-release.yml b/.github/workflows/rust-sdk-release.yml index 9111c91d..4f6a0a97 100644 --- a/.github/workflows/rust-sdk-release.yml +++ b/.github/workflows/rust-sdk-release.yml @@ -89,6 +89,10 @@ jobs: GH_TOKEN: ${{ github.token }} VERSION: ${{ steps.ver.outputs.version }} run: | + if gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then + echo "::notice::GitHub Release $GITHUB_REF_NAME already exists; skipping" + exit 0 + fi gh release create "$GITHUB_REF_NAME" \ --title "dstack-sdk $VERSION" \ --notes "Published to crates.io: [dstack-sdk@$VERSION](https://crates.io/crates/dstack-sdk/$VERSION), [dstack-sdk-types@$VERSION](https://crates.io/crates/dstack-sdk-types/$VERSION)" \