name: release # Release pipeline for Shoal. # # Triggered by pushing a semver tag (e.g. `v0.3.0`). It: # 1. Builds CLI binaries for Linux x86_64, macOS arm64, and macOS x86_64. # 2. Builds the Python SDK sdist + wheel and publishes to PyPI. # 3. Builds and publishes the TypeScript SDK to npm. # 4. Builds and pushes the server Docker image to GHCR. # 5. Packages the Helm chart and pushes it to GHCR as an OCI artifact. # 6. Creates a GitHub Release with the CLI binaries and Helm chart attached. # # Required repository configuration: # - PyPI: configure a Trusted Publisher for this repository/workflow # (https://docs.pypi.org/trusted-publishers/). No token needed. # - npm: add an automation token as the `NPM_TOKEN` repository secret. # - GHCR: works out of the box via the built-in GITHUB_TOKEN. # # Release process is documented in docs/release.md. on: push: tags: - "v*" permissions: contents: write packages: write id-token: write # PyPI trusted publishing + npm provenance env: CARGO_TERM_COLOR: always jobs: cli-binaries: name: CLI binary (${{ matrix.target }}) runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: include: - os: ubuntu-latest target: x86_64-unknown-linux-gnu - os: macos-latest target: aarch64-apple-darwin - os: macos-13 target: x86_64-apple-darwin steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.target }} - uses: Swatinem/rust-cache@v2 with: key: release-${{ matrix.target }} - name: Build CLI run: cargo build --release --manifest-path cli/Cargo.toml --target ${{ matrix.target }} - name: Package binary id: package shell: bash run: | set -euo pipefail # Discover the binary name from the crate metadata instead of # hard-coding it, so renames never break the release pipeline. BIN=$(cargo metadata --manifest-path cli/Cargo.toml --format-version 1 --no-deps \ | jq -r '.packages[0].targets[] | select(.kind | index("bin")) | .name' | head -n1) VERSION="${GITHUB_REF_NAME#v}" OUT="${BIN}-${VERSION}-${{ matrix.target }}" mkdir -p "dist/${OUT}" cp "target/${{ matrix.target }}/release/${BIN}" "dist/${OUT}/" cp cli/README.md "dist/${OUT}/README.md" tar -C dist -czf "dist/${OUT}.tar.gz" "${OUT}" (cd dist && shasum -a 256 "${OUT}.tar.gz" > "${OUT}.tar.gz.sha256") echo "archive=dist/${OUT}.tar.gz" >> "$GITHUB_OUTPUT" - uses: actions/upload-artifact@v4 with: name: cli-${{ matrix.target }} path: | dist/*.tar.gz dist/*.sha256 if-no-files-found: error python-sdk: name: Publish Python SDK to PyPI runs-on: ubuntu-latest environment: release steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.12" - name: Build sdist and wheel run: | python -m pip install --upgrade build python -m build sdks/python - name: Publish to PyPI (trusted publishing) uses: pypa/gh-action-pypi-publish@release/v1 with: packages-dir: sdks/python/dist skip-existing: true typescript-sdk: name: Publish TypeScript SDK to npm runs-on: ubuntu-latest environment: release defaults: run: working-directory: sdks/typescript steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: "20" registry-url: "https://registry.npmjs.org" - name: Install dependencies run: npm install - name: Build run: npm run build - name: Publish run: npm publish --access public --provenance env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} docker-image: name: Build and push Docker image runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - uses: docker/metadata-action@v5 id: meta with: images: ghcr.io/${{ github.repository }} tags: | type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=raw,value=latest - uses: docker/build-push-action@v6 with: context: . file: deploy/docker/Dockerfile push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max helm-chart: name: Package and push Helm chart runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: azure/setup-helm@v4 - name: Lint chart run: helm lint deploy/helm/shoal - name: Package chart run: | set -euo pipefail VERSION="${GITHUB_REF_NAME#v}" mkdir -p dist helm package deploy/helm/shoal \ --version "${VERSION}" \ --app-version "${VERSION}" \ --destination dist - name: Push chart to GHCR (OCI) run: | set -euo pipefail echo "${{ secrets.GITHUB_TOKEN }}" | \ helm registry login ghcr.io --username "${{ github.actor }}" --password-stdin helm push dist/shoal-*.tgz "oci://ghcr.io/${{ github.repository_owner }}/charts" - uses: actions/upload-artifact@v4 with: name: helm-chart path: dist/*.tgz if-no-files-found: error github-release: name: Create GitHub Release runs-on: ubuntu-latest needs: [cli-binaries, helm-chart] steps: - uses: actions/checkout@v4 - uses: actions/download-artifact@v4 with: path: artifacts merge-multiple: true - name: Create release uses: softprops/action-gh-release@v2 with: generate_release_notes: true files: | artifacts/*.tar.gz artifacts/*.sha256 artifacts/*.tgz