From 90536c849ce496672d5443167b0c6f15147c6032 Mon Sep 17 00:00:00 2001 From: Dmitry Shmulevich <17212177+dmitsh@users.noreply.github.com> Date: Thu, 8 Oct 2026 12:07:02 -0700 Subject: [PATCH 1/3] ci(docker): publish artifacts to NGC Signed-off-by: Dmitry Shmulevich <17212177+dmitsh@users.noreply.github.com> --- .github/workflows/docker.yml | 15 ++-- .github/workflows/helm-release.yaml | 46 ++++------- CHANGELOG.md | 4 +- DEVELOPMENT.md | 4 +- README.md | 2 +- SECURITY.md | 4 +- .../render_snapshot_test.yaml.snap | 80 +++++++++---------- charts/topograph/tests/deployment_test.yaml | 2 +- .../topograph/tests/node-observer_test.yaml | 2 +- charts/topograph/values.yaml | 3 +- demos/tas-lab/setup/topograph.sh | 4 +- demos/tas-lab/versions.env | 4 + docs/providers/infiniband.md | 2 +- 13 files changed, 81 insertions(+), 91 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 2412c0b4..68566ebd 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -6,24 +6,21 @@ name: Docker # documentation. on: - push: - tags: - - "v*" workflow_dispatch: permissions: {} env: - REGISTRY: ghcr.io - # github.repository as / - IMAGE_NAME: ${{ github.repository }} + REGISTRY: nvcr.io + # NGC // + IMAGE_NAME: 1070195845945670/topograph/topograph jobs: build: runs-on: linux-amd64-cpu16 + environment: ngc-publish permissions: contents: read - packages: write id-token: write attestations: write artifact-metadata: write @@ -76,8 +73,8 @@ jobs: uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} + username: $oauthtoken + password: ${{ secrets.NGC_API_KEY }} - name: Normalize image name id: image diff --git a/.github/workflows/helm-release.yaml b/.github/workflows/helm-release.yaml index e8a92bd7..148a4cb8 100644 --- a/.github/workflows/helm-release.yaml +++ b/.github/workflows/helm-release.yaml @@ -8,11 +8,15 @@ concurrency: permissions: {} +env: + HELM_REPOSITORY: https://helm.ngc.nvidia.com/1070195845945670/topograph + jobs: release: runs-on: linux-amd64-cpu8 + environment: ngc-publish permissions: - contents: write + contents: read id-token: write attestations: write @@ -33,6 +37,9 @@ jobs: helm lint charts/topograph helm package charts/topograph --destination packaged + - name: Install Helm push plugin + run: helm plugin install https://github.com/chartmuseum/helm-push --version v0.10.4 + - name: Generate SHA-256 checksum run: | set -euo pipefail @@ -47,35 +54,16 @@ jobs: with: subject-path: packaged/topograph-*.tgz - - name: Fetch existing Helm repo index + - name: Publish Helm chart to NGC + env: + HELM_REPO_USERNAME: $oauthtoken + HELM_REPO_PASSWORD: ${{ secrets.NGC_API_KEY }} run: | set -euo pipefail - if ! remote_ref=$(git ls-remote --heads origin refs/heads/gh-pages); then - echo "::error::Failed to check for the existing gh-pages branch." + if [[ -z "${HELM_REPO_PASSWORD}" ]]; then + echo "::error::The NGC_API_KEY secret is required to publish Helm charts." exit 1 fi - if [[ -n "${remote_ref}" ]]; then - git fetch origin refs/heads/gh-pages:refs/remotes/origin/gh-pages - git checkout origin/gh-pages -- index.yaml - fi - - - name: Generate Helm repo index - # Pass context values through env rather than expanding ${{ }} inside - # the script, so the shell never parses them as code. - env: - REPO_OWNER: ${{ github.repository_owner }} - REPO_NAME: ${{ github.event.repository.name }} - run: | - if [ -f index.yaml ]; then - helm repo index packaged --url "https://${REPO_OWNER}.github.io/${REPO_NAME}" --merge index.yaml - else - helm repo index packaged --url "https://${REPO_OWNER}.github.io/${REPO_NAME}" - fi - - - name: Deploy to GitHub Pages - uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4.1.0 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./packaged - publish_branch: gh-pages - keep_files: true + for chart in packaged/topograph-*.tgz; do + helm cm-push "${chart}" "${HELM_REPOSITORY}" + done diff --git a/CHANGELOG.md b/CHANGELOG.md index 0778e49c..bdf17f1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - Firebird obtains one fresh access token per topology generation request and reuses it across all pages, assuming it remains valid for the request duration. Tokens are no longer cached across requests, and HTTP 401 does not trigger reauthentication. +- Manual Helm chart publishing now targets the NGC `dsx-ai-factory-staging/topograph` team instead of GitHub Pages. +- Container image publishing now targets `nvcr.io/dsx-ai-factory-staging/topograph/topograph` in NGC using the `NGC_API_KEY` GitHub Actions secret. - **BREAKING:** The Go module and canonical repository moved from `github.com/NVIDIA/topograph` to `github.com/dsx-ai-factory/topograph`. Repository links, published container and Helm chart locations, build metadata, and examples now use the `dsx-ai-factory` organization. - **BREAKING:** `topograph` and `node-observer` binaries now use [pflag](https://github.com/spf13/pflag) for flag parsing, matching `node-data-broker`. Single-dash long flags (`-version`, `-logtostderr`, `-vmodule`, etc.) now require a double dash (`--version`, `--logtostderr`, `--vmodule`). This affects the use of all [klog](https://github.com/kubernetes/klog) flags - **BREAKING:** `kwok-nodes` now uses [pflag](https://github.com/spf13/pflag) for flag parsing. Single-dash long flags (`-model`, `-output`, etc.) now require a double dash (`--model`, `--output`); the short forms `-m`, `-o`, `-g`, and `-p` are newly added aliases for `--model`, `--output`, `--gpus`, and `--pods` respectively. @@ -43,7 +45,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - Signed SLSA build provenance for published container images and Helm chart packages. Release workflows bind each artifact to its source commit and workflow using GitHub artifact attestations; container provenance is also - published to GHCR alongside the image. + published to NGC alongside the image. - Crusoe provider now derives accelerator domains from `nvidia.com/gpu.clique`, so `topology/block` renders one Slurm block per distinct clique value on rack-scale NVLink systems. The domain is the clique verbatim, matching how the InfiniBand and Lambda AI providers publish theirs. That value is the NVL Partition, which can be finer than the physical NVL Domain when one rack is split into several cliques. The InfiniBand partition is deliberately not used as a fallback domain: a partition can span many racks while a clique cannot, so keying blocks on the partition would let Slurm spread one job across racks and fall back to InfiniBand. Nodes without the clique label get no accelerator domain and are scheduled by the switch tree alone, leaving existing GPU classes unchanged. - Crusoe provider (`crusoe`) and its simulation variant (`crusoe-sim`) discover the InfiniBand switch fabric on Crusoe Cloud from the `crusoe.ai/ib.partition.id` and `crusoe.ai/pod.id` Node labels the Crusoe control plane publishes. Crusoe compute is virtualized, so a guest VM cannot run `ibnetdiscover` and there is no per-node metadata service for switch identity. Fabric tiers are the rail-optimized pod, the InfiniBand partition, and a synthetic root above them. Nodes without those labels are placed under placeholder tiers beneath the same root, so one Slurm tree spans a heterogeneous cluster. Use it with `topology/tree`. - Shared optional provider parameter `imdsUrl` (`providers.GetIMDSURL`, `topology.KeyIMDSURL`) for overriding a provider's default Instance Metadata Service URL. Only the `nscale` provider consumes it so far, falling back to its built-in IMDS URL when unset. diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index d8fade25..f93fe1c2 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -231,15 +231,13 @@ Release and maintenance workflows: - `.github/workflows/release.yml`: on a `v*` tag push, or a manual re-run for an existing tag, checks the tag against `charts/topograph/Chart.yaml` and `CHANGELOG.md`, packages and attests the Helm chart, updates the Helm - repository on `gh-pages`, and creates the GitHub release + repository, and creates the GitHub release - `.github/workflows/publish-fern-docs.yml`: on a `vX.Y.Z` tag push or manual dispatch, publishes the versioned docs site - `.github/workflows/helm-release.yaml`: manual Helm chart publication to `gh-pages` - `.github/workflows/scorecard.yaml`: OpenSSF Scorecard analysis on pushes to `main`, weekly, and on demand -- `.github/workflows/cleanup-untagged-images.yml`: nightly removal of - untagged GHCR images older than 30 days Every `actions/setup-go` step reads the Go version from `go.mod`, so a toolchain bump there also moves the CI jobs that use it. The container diff --git a/README.md b/README.md index 1ecde7f3..2f9afc38 100644 --- a/README.md +++ b/README.md @@ -148,7 +148,7 @@ Topograph sits between topology sources and schedulers, and replaces neither. | Artifact | Where it comes from | |---|---| -| Container image | `ghcr.io/dsx-ai-factory/topograph` | +| Container image | `nvcr.io/nvidia/topograph/topograph` | | Helm chart | [`charts/topograph/`](charts/topograph/) in this repository, published to the chart repository at `https://dsx-ai-factory.github.io/topograph` | | `.deb` package | `make deb`, written to `bin/` | | `.rpm` package | `make rpm`, written to `bin/` | diff --git a/SECURITY.md b/SECURITY.md index 725bad92..371b75df 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -69,7 +69,7 @@ attestation for either, no checksum file, and no assets on their GitHub release expected for those releases, not a supply chain problem. An official release publishes a multi-architecture container image at -`ghcr.io/dsx-ai-factory/topograph:vX.Y.Z`, a Helm chart package in the chart repository at +`nvcr.io/nvidia/topograph/topograph:vX.Y.Z`, a Helm chart package in the chart repository at `https://dsx-ai-factory.github.io/topograph`, and a SHA-256 checksum file beside the chart package. The chart package and its checksum are also attached to the GitHub release. @@ -80,7 +80,7 @@ substituting the release you are checking for `vX.Y.Z` and `X.Y.Z`. Container image: ```bash -gh attestation verify oci://ghcr.io/dsx-ai-factory/topograph:vX.Y.Z \ +gh attestation verify oci://nvcr.io/nvidia/topograph/topograph:vX.Y.Z \ --repo dsx-ai-factory/topograph \ --signer-workflow dsx-ai-factory/topograph/.github/workflows/docker.yml \ --source-ref refs/tags/vX.Y.Z diff --git a/charts/topograph/tests/__snapshot__/render_snapshot_test.yaml.snap b/charts/topograph/tests/__snapshot__/render_snapshot_test.yaml.snap index fcff125c..ed764b9f 100644 --- a/charts/topograph/tests/__snapshot__/render_snapshot_test.yaml.snap +++ b/charts/topograph/tests/__snapshot__/render_snapshot_test.yaml.snap @@ -66,7 +66,7 @@ renders default values.yaml: value: chart-ci-topograph-node-data-broker - name: NODE_DATA_BROKER_NAMESPACE value: topograph - image: ghcr.io/dsx-ai-factory/topograph:v0.0.0 + image: nvcr.io/nvidia/topograph/topograph:v0.0.0 imagePullPolicy: IfNotPresent livenessProbe: httpGet: @@ -168,7 +168,7 @@ renders default values.yaml: valueFrom: fieldRef: fieldPath: spec.nodeName - image: ghcr.io/dsx-ai-factory/topograph:v0.0.0 + image: nvcr.io/nvidia/topograph/topograph:v0.0.0 imagePullPolicy: IfNotPresent livenessProbe: httpGet: @@ -327,7 +327,7 @@ renders default values.yaml: value: chart-ci-topograph-node-data-broker - name: NODE_DATA_BROKER_NAMESPACE value: topograph - image: ghcr.io/dsx-ai-factory/topograph:v0.0.0 + image: nvcr.io/nvidia/topograph/topograph:v0.0.0 imagePullPolicy: IfNotPresent name: node-observer resources: @@ -496,7 +496,7 @@ renders default values.yaml: command: - sh - -c - image: ghcr.io/dsx-ai-factory/topograph:v0.0.0 + image: nvcr.io/nvidia/topograph/topograph:v0.0.0 imagePullPolicy: IfNotPresent name: healthz resources: @@ -548,7 +548,7 @@ renders default values.yaml: command: - sh - -c - image: ghcr.io/dsx-ai-factory/topograph:v0.0.0 + image: nvcr.io/nvidia/topograph/topograph:v0.0.0 imagePullPolicy: IfNotPresent name: metrics resources: @@ -641,7 +641,7 @@ renders values.k8s.gateway-api-example.yaml: value: chart-ci-topograph-node-data-broker - name: NODE_DATA_BROKER_NAMESPACE value: topograph - image: ghcr.io/dsx-ai-factory/topograph:v0.0.0 + image: nvcr.io/nvidia/topograph/topograph:v0.0.0 imagePullPolicy: IfNotPresent livenessProbe: httpGet: @@ -765,7 +765,7 @@ renders values.k8s.gateway-api-example.yaml: valueFrom: fieldRef: fieldPath: spec.nodeName - image: ghcr.io/dsx-ai-factory/topograph:v0.0.0 + image: nvcr.io/nvidia/topograph/topograph:v0.0.0 imagePullPolicy: IfNotPresent livenessProbe: httpGet: @@ -924,7 +924,7 @@ renders values.k8s.gateway-api-example.yaml: value: chart-ci-topograph-node-data-broker - name: NODE_DATA_BROKER_NAMESPACE value: topograph - image: ghcr.io/dsx-ai-factory/topograph:v0.0.0 + image: nvcr.io/nvidia/topograph/topograph:v0.0.0 imagePullPolicy: IfNotPresent name: node-observer resources: @@ -1093,7 +1093,7 @@ renders values.k8s.gateway-api-example.yaml: command: - sh - -c - image: ghcr.io/dsx-ai-factory/topograph:v0.0.0 + image: nvcr.io/nvidia/topograph/topograph:v0.0.0 imagePullPolicy: IfNotPresent name: healthz resources: @@ -1145,7 +1145,7 @@ renders values.k8s.gateway-api-example.yaml: command: - sh - -c - image: ghcr.io/dsx-ai-factory/topograph:v0.0.0 + image: nvcr.io/nvidia/topograph/topograph:v0.0.0 imagePullPolicy: IfNotPresent name: metrics resources: @@ -1240,7 +1240,7 @@ renders values.k8s.gcp-federated-workload-identity-example.yaml: value: topograph - name: GOOGLE_APPLICATION_CREDENTIALS value: /etc/topograph/gcp/credentials-config.json - image: ghcr.io/dsx-ai-factory/topograph:v0.0.0 + image: nvcr.io/nvidia/topograph/topograph:v0.0.0 imagePullPolicy: IfNotPresent livenessProbe: httpGet: @@ -1362,7 +1362,7 @@ renders values.k8s.gcp-federated-workload-identity-example.yaml: valueFrom: fieldRef: fieldPath: spec.nodeName - image: ghcr.io/dsx-ai-factory/topograph:v0.0.0 + image: nvcr.io/nvidia/topograph/topograph:v0.0.0 imagePullPolicy: IfNotPresent livenessProbe: httpGet: @@ -1528,7 +1528,7 @@ renders values.k8s.gcp-federated-workload-identity-example.yaml: value: chart-ci-topograph-node-data-broker - name: NODE_DATA_BROKER_NAMESPACE value: topograph - image: ghcr.io/dsx-ai-factory/topograph:v0.0.0 + image: nvcr.io/nvidia/topograph/topograph:v0.0.0 imagePullPolicy: IfNotPresent name: node-observer resources: @@ -1704,7 +1704,7 @@ renders values.k8s.gcp-federated-workload-identity-example.yaml: command: - sh - -c - image: ghcr.io/dsx-ai-factory/topograph:v0.0.0 + image: nvcr.io/nvidia/topograph/topograph:v0.0.0 imagePullPolicy: IfNotPresent name: healthz resources: @@ -1756,7 +1756,7 @@ renders values.k8s.gcp-federated-workload-identity-example.yaml: command: - sh - -c - image: ghcr.io/dsx-ai-factory/topograph:v0.0.0 + image: nvcr.io/nvidia/topograph/topograph:v0.0.0 imagePullPolicy: IfNotPresent name: metrics resources: @@ -1851,7 +1851,7 @@ renders values.k8s.gcp-service-account-example.yaml: value: topograph - name: GOOGLE_APPLICATION_CREDENTIALS value: /etc/topograph/gcp/service-account-keys.json - image: ghcr.io/dsx-ai-factory/topograph:v0.0.0 + image: nvcr.io/nvidia/topograph/topograph:v0.0.0 imagePullPolicy: IfNotPresent livenessProbe: httpGet: @@ -1961,7 +1961,7 @@ renders values.k8s.gcp-service-account-example.yaml: valueFrom: fieldRef: fieldPath: spec.nodeName - image: ghcr.io/dsx-ai-factory/topograph:v0.0.0 + image: nvcr.io/nvidia/topograph/topograph:v0.0.0 imagePullPolicy: IfNotPresent livenessProbe: httpGet: @@ -2125,7 +2125,7 @@ renders values.k8s.gcp-service-account-example.yaml: value: chart-ci-topograph-node-data-broker - name: NODE_DATA_BROKER_NAMESPACE value: topograph - image: ghcr.io/dsx-ai-factory/topograph:v0.0.0 + image: nvcr.io/nvidia/topograph/topograph:v0.0.0 imagePullPolicy: IfNotPresent name: node-observer resources: @@ -2301,7 +2301,7 @@ renders values.k8s.gcp-service-account-example.yaml: command: - sh - -c - image: ghcr.io/dsx-ai-factory/topograph:v0.0.0 + image: nvcr.io/nvidia/topograph/topograph:v0.0.0 imagePullPolicy: IfNotPresent name: healthz resources: @@ -2353,7 +2353,7 @@ renders values.k8s.gcp-service-account-example.yaml: command: - sh - -c - image: ghcr.io/dsx-ai-factory/topograph:v0.0.0 + image: nvcr.io/nvidia/topograph/topograph:v0.0.0 imagePullPolicy: IfNotPresent name: metrics resources: @@ -2449,7 +2449,7 @@ renders values.k8s.ib-example.yaml: value: chart-ci-topograph-node-data-broker - name: NODE_DATA_BROKER_NAMESPACE value: topograph - image: ghcr.io/dsx-ai-factory/topograph:v0.0.0 + image: nvcr.io/nvidia/topograph/topograph:v0.0.0 imagePullPolicy: IfNotPresent livenessProbe: httpGet: @@ -2556,7 +2556,7 @@ renders values.k8s.ib-example.yaml: valueFrom: fieldRef: fieldPath: spec.nodeName - image: ghcr.io/dsx-ai-factory/topograph:v0.0.0 + image: nvcr.io/nvidia/topograph/topograph:v0.0.0 imagePullPolicy: IfNotPresent livenessProbe: httpGet: @@ -2730,7 +2730,7 @@ renders values.k8s.ib-example.yaml: value: chart-ci-topograph-node-data-broker - name: NODE_DATA_BROKER_NAMESPACE value: topograph - image: ghcr.io/dsx-ai-factory/topograph:v0.0.0 + image: nvcr.io/nvidia/topograph/topograph:v0.0.0 imagePullPolicy: IfNotPresent name: node-observer resources: @@ -2924,7 +2924,7 @@ renders values.k8s.ib-example.yaml: command: - sh - -c - image: ghcr.io/dsx-ai-factory/topograph:v0.0.0 + image: nvcr.io/nvidia/topograph/topograph:v0.0.0 imagePullPolicy: IfNotPresent name: healthz resources: @@ -2976,7 +2976,7 @@ renders values.k8s.ib-example.yaml: command: - sh - -c - image: ghcr.io/dsx-ai-factory/topograph:v0.0.0 + image: nvcr.io/nvidia/topograph/topograph:v0.0.0 imagePullPolicy: IfNotPresent name: metrics resources: @@ -3069,7 +3069,7 @@ renders values.slinky.block-example.yaml: value: chart-ci-topograph-node-data-broker - name: NODE_DATA_BROKER_NAMESPACE value: topograph - image: ghcr.io/dsx-ai-factory/topograph:v0.0.0 + image: nvcr.io/nvidia/topograph/topograph:v0.0.0 imagePullPolicy: IfNotPresent livenessProbe: httpGet: @@ -3178,7 +3178,7 @@ renders values.slinky.block-example.yaml: valueFrom: fieldRef: fieldPath: spec.nodeName - image: ghcr.io/dsx-ai-factory/topograph:v0.0.0 + image: nvcr.io/nvidia/topograph/topograph:v0.0.0 imagePullPolicy: IfNotPresent livenessProbe: httpGet: @@ -3355,7 +3355,7 @@ renders values.slinky.block-example.yaml: value: chart-ci-topograph-node-data-broker - name: NODE_DATA_BROKER_NAMESPACE value: topograph - image: ghcr.io/dsx-ai-factory/topograph:v0.0.0 + image: nvcr.io/nvidia/topograph/topograph:v0.0.0 imagePullPolicy: IfNotPresent name: node-observer resources: @@ -3538,7 +3538,7 @@ renders values.slinky.block-example.yaml: command: - sh - -c - image: ghcr.io/dsx-ai-factory/topograph:v0.0.0 + image: nvcr.io/nvidia/topograph/topograph:v0.0.0 imagePullPolicy: IfNotPresent name: healthz resources: @@ -3590,7 +3590,7 @@ renders values.slinky.block-example.yaml: command: - sh - -c - image: ghcr.io/dsx-ai-factory/topograph:v0.0.0 + image: nvcr.io/nvidia/topograph/topograph:v0.0.0 imagePullPolicy: IfNotPresent name: metrics resources: @@ -3683,7 +3683,7 @@ renders values.slinky.partition-example.yaml: value: chart-ci-topograph-node-data-broker - name: NODE_DATA_BROKER_NAMESPACE value: topograph - image: ghcr.io/dsx-ai-factory/topograph:v0.0.0 + image: nvcr.io/nvidia/topograph/topograph:v0.0.0 imagePullPolicy: IfNotPresent livenessProbe: httpGet: @@ -3789,7 +3789,7 @@ renders values.slinky.partition-example.yaml: valueFrom: fieldRef: fieldPath: spec.nodeName - image: ghcr.io/dsx-ai-factory/topograph:v0.0.0 + image: nvcr.io/nvidia/topograph/topograph:v0.0.0 imagePullPolicy: IfNotPresent livenessProbe: httpGet: @@ -3983,7 +3983,7 @@ renders values.slinky.partition-example.yaml: value: chart-ci-topograph-node-data-broker - name: NODE_DATA_BROKER_NAMESPACE value: topograph - image: ghcr.io/dsx-ai-factory/topograph:v0.0.0 + image: nvcr.io/nvidia/topograph/topograph:v0.0.0 imagePullPolicy: IfNotPresent name: node-observer resources: @@ -4167,7 +4167,7 @@ renders values.slinky.partition-example.yaml: command: - sh - -c - image: ghcr.io/dsx-ai-factory/topograph:v0.0.0 + image: nvcr.io/nvidia/topograph/topograph:v0.0.0 imagePullPolicy: IfNotPresent name: healthz resources: @@ -4219,7 +4219,7 @@ renders values.slinky.partition-example.yaml: command: - sh - -c - image: ghcr.io/dsx-ai-factory/topograph:v0.0.0 + image: nvcr.io/nvidia/topograph/topograph:v0.0.0 imagePullPolicy: IfNotPresent name: metrics resources: @@ -4312,7 +4312,7 @@ renders values.slinky.tree-example.yaml: value: chart-ci-topograph-node-data-broker - name: NODE_DATA_BROKER_NAMESPACE value: topograph - image: ghcr.io/dsx-ai-factory/topograph:v0.0.0 + image: nvcr.io/nvidia/topograph/topograph:v0.0.0 imagePullPolicy: IfNotPresent livenessProbe: httpGet: @@ -4418,7 +4418,7 @@ renders values.slinky.tree-example.yaml: valueFrom: fieldRef: fieldPath: spec.nodeName - image: ghcr.io/dsx-ai-factory/topograph:v0.0.0 + image: nvcr.io/nvidia/topograph/topograph:v0.0.0 imagePullPolicy: IfNotPresent livenessProbe: httpGet: @@ -4587,7 +4587,7 @@ renders values.slinky.tree-example.yaml: value: chart-ci-topograph-node-data-broker - name: NODE_DATA_BROKER_NAMESPACE value: topograph - image: ghcr.io/dsx-ai-factory/topograph:v0.0.0 + image: nvcr.io/nvidia/topograph/topograph:v0.0.0 imagePullPolicy: IfNotPresent name: node-observer resources: @@ -4770,7 +4770,7 @@ renders values.slinky.tree-example.yaml: command: - sh - -c - image: ghcr.io/dsx-ai-factory/topograph:v0.0.0 + image: nvcr.io/nvidia/topograph/topograph:v0.0.0 imagePullPolicy: IfNotPresent name: healthz resources: @@ -4822,7 +4822,7 @@ renders values.slinky.tree-example.yaml: command: - sh - -c - image: ghcr.io/dsx-ai-factory/topograph:v0.0.0 + image: nvcr.io/nvidia/topograph/topograph:v0.0.0 imagePullPolicy: IfNotPresent name: metrics resources: diff --git a/charts/topograph/tests/deployment_test.yaml b/charts/topograph/tests/deployment_test.yaml index b3867275..abff09eb 100644 --- a/charts/topograph/tests/deployment_test.yaml +++ b/charts/topograph/tests/deployment_test.yaml @@ -102,7 +102,7 @@ tests: asserts: - equal: path: spec.template.spec.containers[0].image - value: ghcr.io/dsx-ai-factory/topograph:v1.2.3 + value: nvcr.io/nvidia/topograph/topograph:v1.2.3 template: templates/deployment.yaml - it: a custom repository still derives its tag from the appVersion diff --git a/charts/topograph/tests/node-observer_test.yaml b/charts/topograph/tests/node-observer_test.yaml index f0b09509..7d9669d4 100644 --- a/charts/topograph/tests/node-observer_test.yaml +++ b/charts/topograph/tests/node-observer_test.yaml @@ -125,7 +125,7 @@ tests: asserts: - matchRegex: path: spec.template.spec.containers[0].image - pattern: "^ghcr.io/dsx-ai-factory/topograph:v[0-9]+\\.[0-9]+\\.[0-9]+(-[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?$" + pattern: "^nvcr.io/nvidia/topograph/topograph:v[0-9]+\\.[0-9]+\\.[0-9]+(-[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?$" - it: uses the shared root container settings templates: diff --git a/charts/topograph/values.yaml b/charts/topograph/values.yaml index 763a4807..ceddc7e1 100644 --- a/charts/topograph/values.yaml +++ b/charts/topograph/values.yaml @@ -55,8 +55,9 @@ service: replicaCount: 1 +# Official NGC container image shared by all Topograph components. image: - repository: ghcr.io/dsx-ai-factory/topograph + repository: nvcr.io/nvidia/topograph/topograph # Overrides the image tag whose default is the chart appVersion. tag: "" pullPolicy: IfNotPresent diff --git a/demos/tas-lab/setup/topograph.sh b/demos/tas-lab/setup/topograph.sh index 885c4f03..eb103491 100755 --- a/demos/tas-lab/setup/topograph.sh +++ b/demos/tas-lab/setup/topograph.sh @@ -16,7 +16,7 @@ set -euo pipefail source "$(dirname "${BASH_SOURCE[0]}")/../lib/common.sh" -image=ghcr.io/dsx-ai-factory/topograph:tas-lab +image=nvcr.io/nvidia/topograph/topograph:$TOPOGRAPH_VERSION topograph_ns=topograph labels() { @@ -46,7 +46,7 @@ install) # The image runs in kind nodes, so build for the container engine's architecture, not the host's. arch="${GOARCH:-$(docker info --format '{{.Architecture}}' | sed 's/aarch64/arm64/; s/x86_64/amd64/')}" (cd "$repo_root" && make image-build GOOS=linux GOARCH="$arch" \ - IMAGE_REPO=ghcr.io/dsx-ai-factory/topograph IMAGE_TAG=tas-lab) + IMAGE_REPO=nvcr.io/nvidia/topograph/topograph IMAGE_TAG=tas-lab) archive_dir="$(mktemp -d)" docker save -o "$archive_dir/topograph.tar" "$image" kind load image-archive "$archive_dir/topograph.tar" --name tas-lab diff --git a/demos/tas-lab/versions.env b/demos/tas-lab/versions.env index bbc32ae5..00ba4d89 100644 --- a/demos/tas-lab/versions.env +++ b/demos/tas-lab/versions.env @@ -25,6 +25,10 @@ LWS_VERSION="${LWS_VERSION:-v0.11.1}" # Chainsaw CLI installed by setup/chainsaw.sh (runs the declarative scenarios under scenarios/). CHAINSAW_VERSION="${CHAINSAW_VERSION:-v0.2.15}" +# Topograph version +TOPOGRAPH_IMG_URL="${TOPOGRAPH_IMG_URL:-nvcr.io/nvidia/topograph/topograph}" +TOPOGRAPH_IMG_TAG="${TOPOGRAPH_IMG_TAG:-v1.1.0}" + # After a version bump, also re-check what is tied to these versions: # - the Workload/PodGroup API group version (the scheduling.k8s.io manifests in scenarios/0[1-4]*, setup/kind-config.yaml # runtimeConfig) and the feature gate names in setup/kind-config.yaml, which depend on diff --git a/docs/providers/infiniband.md b/docs/providers/infiniband.md index f08b00a0..f04a595e 100644 --- a/docs/providers/infiniband.md +++ b/docs/providers/infiniband.md @@ -95,7 +95,7 @@ For the Slurm engine, verify the generated `topology.conf` reflects the expected ### Prerequisites - Topograph deployed via Helm — when `accelerator.source` is `nvidia-smi`, the node-data-broker DaemonSet collects NVLink partition IDs from each node and stores them as Kubernetes node annotations (`topograph.run/cluster-id`). With `kubernetes-label` or `none`, the broker skips that collection. -- The default **`ghcr.io/dsx-ai-factory/topograph`** image includes **`ibnetdiscover`** (Alpine `rdma-core`). No separate InfiniBand image is required. IB deployments typically run the broker **privileged** and mount host **`/sys/class`** so `ibnetdiscover` can reach IB devices — see [`values.k8s.ib-example.yaml`](https://github.com/dsx-ai-factory/topograph/blob/main/charts/topograph/values.k8s.ib-example.yaml). +- The default **`nvcr.io/nvidia/topograph/topograph`** image includes **`ibnetdiscover`** (Alpine `rdma-core`). No separate InfiniBand image is required. IB deployments typically run the broker **privileged** and mount host **`/sys/class`** so `ibnetdiscover` can reach IB devices — see [`values.k8s.ib-example.yaml`](../../charts/topograph/values.k8s.ib-example.yaml). - NVIDIA GPU Operator — standard on NVIDIA GPU Kubernetes clusters; manages the device plugin DaemonSet used to read NVLink clique IDs. Required only for NVLink domain discovery; on clusters without NVLink-connected GPUs this does not apply and the provider will still discover the IB switch tree. ### How It Works From cad751547bf361e1c74cfe729606b2bb82048606 Mon Sep 17 00:00:00 2001 From: Ravi Shankar Date: Mon, 19 Oct 2026 11:20:17 -0700 Subject: [PATCH 2/3] updated tas-lab scripts to use the image from configured repo and tag Signed-off-by: Ravi Shankar --- demos/tas-lab/README.md | 12 ++++++------ demos/tas-lab/setup/topograph.sh | 23 ++++++++++++++--------- demos/tas-lab/setup/values.topograph.yaml | 8 ++++---- demos/tas-lab/teardown/teardown.sh | 5 +++-- 4 files changed, 27 insertions(+), 21 deletions(-) diff --git a/demos/tas-lab/README.md b/demos/tas-lab/README.md index 0a1db30a..3b551080 100644 --- a/demos/tas-lab/README.md +++ b/demos/tas-lab/README.md @@ -59,8 +59,8 @@ sub-domains; scenarios 04 and 08), [`tas-lab-nodomain.yaml`](../../tests/models/ (multitier with no domain on one node; scenarios 02 and 08) and [`tas-lab-crossing.yaml`](../../tests/models/tas-lab-crossing.yaml) (a domain that spans two spines; scenario 08). Scenarios switch between them themselves. The test provider only reads -models embedded in the Topograph binary, which is why `setup/topograph.sh` builds the image from -this checkout. +models embedded in the Topograph binary, so the image that `setup/topograph.sh` pulls +(`TOPOGRAPH_IMG_URL` and `TOPOGRAPH_IMG_TAG` in `versions.env`) must contain these models. Each case is a [Chainsaw](https://kyverno.github.io/chainsaw/) test: it makes sure Topograph's labels are in place, creates what it needs in a namespace of its own, asserts the outcome and removes @@ -68,9 +68,9 @@ everything it created. You can run any scenario at any time after setup. ## Setup -Prerequisites: `kind`, `kubectl`, `helm`, `curl`, `make`, `python3`, and a -container engine reachable as `docker` (podman's Docker shim works). The container engine needs a few GB free: the Topograph -build pulls a Go image and every kind node imports the Topograph image. +Prerequisites: `kind`, `kubectl`, `helm`, `curl`, `python3`, and a +container engine reachable as `docker` (podman's Docker shim works). The container engine needs a few GB free: it pulls the Topograph +image and every kind node imports it. ```bash cd demos/tas-lab @@ -318,4 +318,4 @@ the `ResourceFlavor` and queues over each Topology. This deletes the kind cluster (Topograph and Kueue go with it) and the scratch files the scenarios write under `${TMPDIR:-/tmp}/tas-lab-`. The Topograph image stays in the local image -store; remove it with `docker rmi ghcr.io/dsx-ai-factory/topograph:tas-lab`. +store; remove it with `docker rmi "$TOPOGRAPH_IMG_URL:$TOPOGRAPH_IMG_TAG"` (values in `versions.env`). diff --git a/demos/tas-lab/setup/topograph.sh b/demos/tas-lab/setup/topograph.sh index eb103491..e5907ecb 100755 --- a/demos/tas-lab/setup/topograph.sh +++ b/demos/tas-lab/setup/topograph.sh @@ -5,7 +5,8 @@ # Setup step 2: install Topograph, which labels the workers, and show the labels. # -# setup/topograph.sh install build the image, load it into kind, helm install, wait for labels +# setup/topograph.sh install pull $TOPOGRAPH_IMG_URL:$TOPOGRAPH_IMG_TAG (versions.env), load it into +# kind, helm install, wait for labels # setup/topograph.sh generate [model] [engine-params-json] # ask the API server to regenerate and re-apply the labels, # optionally from another model in tests/models (default @@ -16,7 +17,7 @@ set -euo pipefail source "$(dirname "${BASH_SOURCE[0]}")/../lib/common.sh" -image=nvcr.io/nvidia/topograph/topograph:$TOPOGRAPH_VERSION +image=$TOPOGRAPH_IMG_URL:$TOPOGRAPH_IMG_TAG topograph_ns=topograph labels() { @@ -42,17 +43,21 @@ wait_for_api 30 case "${1:-}" in install) - # The test provider only reads models embedded in the binary, so build from this checkout. - # The image runs in kind nodes, so build for the container engine's architecture, not the host's. - arch="${GOARCH:-$(docker info --format '{{.Architecture}}' | sed 's/aarch64/arm64/; s/x86_64/amd64/')}" - (cd "$repo_root" && make image-build GOOS=linux GOARCH="$arch" \ - IMAGE_REPO=nvcr.io/nvidia/topograph/topograph IMAGE_TAG=tas-lab) + # Pull the published image and load it into kind, so the nodes need no registry access. + # The test provider reads models embedded in the binary, so the image must already contain + # tests/models/tas-lab.yaml; pick a TOPOGRAPH_IMG_TAG that does. + docker pull "$image" archive_dir="$(mktemp -d)" docker save -o "$archive_dir/topograph.tar" "$image" kind load image-archive "$archive_dir/topograph.tar" --name tas-lab rm -rf "$archive_dir" - KUBE_CONTEXT=$ctx "$repo_root/scripts/install-topograph.sh" "$lab_dir/setup/values.topograph.yaml" - # The image tag is fixed, so a re-install with a rebuilt image needs the pods restarted. + # install-topograph.sh takes one values file, so add the image from versions.env to a copy of the lab values. + values_file="$(mktemp)" + awk -v repo="$TOPOGRAPH_IMG_URL" -v tag="$TOPOGRAPH_IMG_TAG" \ + '{ print } /^image:$/ { printf " repository: %s\n tag: \"%s\"\n", repo, tag }' \ + "$lab_dir/setup/values.topograph.yaml" >"$values_file" + KUBE_CONTEXT=$ctx "$repo_root/scripts/install-topograph.sh" "$values_file" + rm -f "$values_file" kubectl -n $topograph_ns rollout restart deploy kubectl -n $topograph_ns rollout status deploy --timeout=120s wait_for_labels diff --git a/demos/tas-lab/setup/values.topograph.yaml b/demos/tas-lab/setup/values.topograph.yaml index 34a91aec..1ecd87aa 100644 --- a/demos/tas-lab/setup/values.topograph.yaml +++ b/demos/tas-lab/setup/values.topograph.yaml @@ -3,11 +3,11 @@ # Topograph for the TAS lab: the test provider replays tests/models/tas-lab.yaml (two leaf # switches under one spine) and the k8s engine writes fabric.topograph.run/tier-N on the -# four kind workers. The image is built from this checkout so the model is embedded in it. +# four kind workers. setup/topograph.sh pulls the image named by TOPOGRAPH_IMG_URL and +# TOPOGRAPH_IMG_TAG (versions.env), loads it into kind, and adds image.repository and image.tag +# to a copy of this file before installing; the image must embed the tas-lab model. image: - repository: ghcr.io/dsx-ai-factory/topograph - tag: tas-lab - pullPolicy: Never + pullPolicy: IfNotPresent provider: name: test diff --git a/demos/tas-lab/teardown/teardown.sh b/demos/tas-lab/teardown/teardown.sh index d7569908..5eda67f4 100755 --- a/demos/tas-lab/teardown/teardown.sh +++ b/demos/tas-lab/teardown/teardown.sh @@ -10,5 +10,6 @@ source "$(dirname "${BASH_SOURCE[0]}")/../lib/common.sh" kind delete cluster --name tas-lab rm -rf "$state_dir" # The Chainsaw CLI in .bin/ (setup/chainsaw.sh) stays; it is a tool, not cluster state. -# The image built by setup/topograph.sh stays in the local image store; remove it with: -# docker rmi ghcr.io/dsx-ai-factory/topograph:tas-lab +# The image pulled by setup/topograph.sh stays in the local image store; remove it with +# (values in versions.env): +# docker rmi "$TOPOGRAPH_IMG_URL:$TOPOGRAPH_IMG_TAG" From 475da896bed629c332598160a2cdc38cd030dc85 Mon Sep 17 00:00:00 2001 From: Dmitry Shmulevich <17212177+dmitsh@users.noreply.github.com> Date: Tue, 20 Oct 2026 10:51:52 -0700 Subject: [PATCH 3/3] update helm chart repo Signed-off-by: Dmitry Shmulevich <17212177+dmitsh@users.noreply.github.com> --- .github/workflows/cleanup-untagged-images.yml | 67 --- .github/workflows/docker-gh.yml | 130 ++++++ .github/workflows/docker.yml | 17 +- .github/workflows/helm-release-gh.yaml | 81 ++++ .github/workflows/helm-release.yaml | 49 ++- .github/workflows/release.yml | 164 ------- CHANGELOG.md | 14 +- DEVELOPMENT.md | 32 +- GOVERNANCE.md | 7 +- README.md | 4 +- RELEASE.md | 405 ++++++++---------- SECURITY.md | 16 +- charts/topograph/README.md | 2 +- docs/engines/k8s.md | 4 +- docs/get-started/quickstart-k8s.md | 2 +- docs/providers/lambdai.md | 6 +- scripts/create-release-tag.sh | 6 +- 17 files changed, 486 insertions(+), 520 deletions(-) delete mode 100644 .github/workflows/cleanup-untagged-images.yml create mode 100644 .github/workflows/docker-gh.yml create mode 100644 .github/workflows/helm-release-gh.yaml delete mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/cleanup-untagged-images.yml b/.github/workflows/cleanup-untagged-images.yml deleted file mode 100644 index 4fd6fcc8..00000000 --- a/.github/workflows/cleanup-untagged-images.yml +++ /dev/null @@ -1,67 +0,0 @@ -# Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -name: Cleanup Untagged Container Images - -on: - schedule: - - cron: '0 0 * * *' - workflow_dispatch: - inputs: - dry_run: - description: 'Only show the images that would be deleted' - required: true - default: 'true' - type: choice - options: - - 'true' - - 'false' - cut_off: - description: 'Delete images older than this (for example, 1d, 7d, or 30d)' - required: true - default: '30d' - type: string - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: false - -permissions: - contents: read - -jobs: - cleanup-untagged-images: - name: Cleanup Untagged Images - if: github.repository == 'dsx-ai-factory/topograph' - runs-on: linux-amd64-cpu32 - timeout-minutes: 30 - permissions: - # snok/container-retention-policy deletes package versions through the - # GitHub packages API, which the action documents as requiring - # packages:write for an installation token such as GITHUB_TOKEN. - contents: read - packages: write - - steps: - - name: Delete stale untagged images - uses: snok/container-retention-policy@d3bdcf5ce9b05f685154e4a16c39233b245e3d53 # v3.1.0 - with: - account: dsx-ai-factory - token: ${{ secrets.GITHUB_TOKEN }} - image-names: topograph - cut-off: ${{ github.event.inputs.cut_off || '30d' }} - keep-n-most-recent: 5 - dry-run: ${{ github.event.inputs.dry_run || 'false' }} - tag-selection: untagged - timestamp-to-use: updated_at diff --git a/.github/workflows/docker-gh.yml b/.github/workflows/docker-gh.yml new file mode 100644 index 00000000..2412c0b4 --- /dev/null +++ b/.github/workflows/docker-gh.yml @@ -0,0 +1,130 @@ +name: Docker + +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +on: + push: + tags: + - "v*" + workflow_dispatch: + +permissions: {} + +env: + REGISTRY: ghcr.io + # github.repository as / + IMAGE_NAME: ${{ github.repository }} + +jobs: + build: + runs-on: linux-amd64-cpu16 + permissions: + contents: read + packages: write + id-token: write + attestations: write + artifact-metadata: write + + steps: + - name: Checkout repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Validate manual branch + if: github.event_name == 'workflow_dispatch' && github.ref_type == 'branch' + env: + BRANCH_NAME: ${{ github.ref_name }} + run: | + if [[ "${BRANCH_NAME}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "::error::Branch ${BRANCH_NAME} conflicts with the official release tag namespace." + echo "::error::Publish official releases by pushing the matching Git tag instead." + exit 1 + fi + + - name: Validate release tag + if: github.ref_type == 'tag' + env: + RELEASE_TAG: ${{ github.ref_name }} + run: | + set -euo pipefail + + if [[ ! "${RELEASE_TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "::error::Release tag must match vX.Y.Z; got ${RELEASE_TAG}" + exit 1 + fi + + version=${RELEASE_TAG#v} + chart_version=$(awk '$1 == "version:" {gsub(/"/, "", $2); print $2; exit}' charts/topograph/Chart.yaml) + app_version=$(awk '$1 == "appVersion:" {gsub(/"/, "", $2); print $2; exit}' charts/topograph/Chart.yaml) + + if [[ "${chart_version}" != "${version}" ]]; then + echo "::error::Chart version ${chart_version} does not match release version ${version}." + exit 1 + fi + if [[ "${app_version}" != "${RELEASE_TAG}" ]]; then + echo "::error::Chart appVersion ${app_version} does not match release tag ${RELEASE_TAG}." + exit 1 + fi + + # Login against a Docker registry except on PR + # https://github.com/docker/login-action + - name: Log into registry ${{ env.REGISTRY }} + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Normalize image name + id: image + env: + IMAGE: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + run: echo "name=${IMAGE,,}" >> "$GITHUB_OUTPUT" + + # Extract metadata (tags, labels) for Docker + # https://github.com/docker/metadata-action + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 + with: + flavor: latest=false + images: ${{ steps.image.outputs.name }} + tags: | + type=ref,event=tag + type=ref,event=branch + type=sha,priority=100,prefix=,suffix=,format=short + + # Set up QEMU for cross-platform builds + - name: Set up QEMU + uses: docker/setup-qemu-action@99012661954931238ded8c8b007157a8430204e1 # v4.4.0 + + # Set up Docker Buildx + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1 + with: + buildkitd-config: /etc/buildkit/buildkitd.toml + + # Build and push Docker image with Buildx (don't push on PR) + # https://github.com/docker/build-push-action + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 + with: + context: . + file: ./Dockerfile + push: ${{ github.event_name != 'pull_request' }} + platforms: linux/amd64,linux/arm64 + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + provenance: mode=max,version=v1 + + - name: Generate SLSA provenance + uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2 + with: + subject-name: ${{ steps.image.outputs.name }} + subject-digest: ${{ steps.build-and-push.outputs.digest }} + push-to-registry: true diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 68566ebd..3e5f40c7 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -31,26 +31,15 @@ jobs: with: persist-credentials: false - - name: Validate manual branch - if: github.event_name == 'workflow_dispatch' && github.ref_type == 'branch' - env: - BRANCH_NAME: ${{ github.ref_name }} - run: | - if [[ "${BRANCH_NAME}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "::error::Branch ${BRANCH_NAME} conflicts with the official release tag namespace." - echo "::error::Publish official releases by pushing the matching Git tag instead." - exit 1 - fi - - - name: Validate release tag - if: github.ref_type == 'tag' + - name: Validate release version + if: github.ref_type == 'tag' || startsWith(github.ref_name, 'v') env: RELEASE_TAG: ${{ github.ref_name }} run: | set -euo pipefail if [[ ! "${RELEASE_TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "::error::Release tag must match vX.Y.Z; got ${RELEASE_TAG}" + echo "::error::Release version must match vX.Y.Z; got ${RELEASE_TAG}" exit 1 fi diff --git a/.github/workflows/helm-release-gh.yaml b/.github/workflows/helm-release-gh.yaml new file mode 100644 index 00000000..e8a92bd7 --- /dev/null +++ b/.github/workflows/helm-release-gh.yaml @@ -0,0 +1,81 @@ +name: Release Helm Charts + +on: workflow_dispatch + +concurrency: + group: helm-repository-publication + cancel-in-progress: false + +permissions: {} + +jobs: + release: + runs-on: linux-amd64-cpu8 + permissions: + contents: write + id-token: write + attestations: write + + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Set up Helm + uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1 + with: + version: v3.14.0 + + - name: Package Helm chart + run: | + mkdir -p packaged + helm lint charts/topograph + helm package charts/topograph --destination packaged + + - name: Generate SHA-256 checksum + run: | + set -euo pipefail + cd packaged + for chart in topograph-*.tgz; do + sha256sum "${chart}" > "${chart}.sha256" + sha256sum --check "${chart}.sha256" + done + + - name: Generate SLSA provenance + uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2 + with: + subject-path: packaged/topograph-*.tgz + + - name: Fetch existing Helm repo index + run: | + set -euo pipefail + if ! remote_ref=$(git ls-remote --heads origin refs/heads/gh-pages); then + echo "::error::Failed to check for the existing gh-pages branch." + exit 1 + fi + if [[ -n "${remote_ref}" ]]; then + git fetch origin refs/heads/gh-pages:refs/remotes/origin/gh-pages + git checkout origin/gh-pages -- index.yaml + fi + + - name: Generate Helm repo index + # Pass context values through env rather than expanding ${{ }} inside + # the script, so the shell never parses them as code. + env: + REPO_OWNER: ${{ github.repository_owner }} + REPO_NAME: ${{ github.event.repository.name }} + run: | + if [ -f index.yaml ]; then + helm repo index packaged --url "https://${REPO_OWNER}.github.io/${REPO_NAME}" --merge index.yaml + else + helm repo index packaged --url "https://${REPO_OWNER}.github.io/${REPO_NAME}" + fi + + - name: Deploy to GitHub Pages + uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4.1.0 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./packaged + publish_branch: gh-pages + keep_files: true diff --git a/.github/workflows/helm-release.yaml b/.github/workflows/helm-release.yaml index 148a4cb8..cdb213c0 100644 --- a/.github/workflows/helm-release.yaml +++ b/.github/workflows/helm-release.yaml @@ -9,7 +9,7 @@ concurrency: permissions: {} env: - HELM_REPOSITORY: https://helm.ngc.nvidia.com/1070195845945670/topograph + HELM_REPOSITORY: nvcr.io/1070195845945670/topograph jobs: release: @@ -37,8 +37,10 @@ jobs: helm lint charts/topograph helm package charts/topograph --destination packaged - - name: Install Helm push plugin - run: helm plugin install https://github.com/chartmuseum/helm-push --version v0.10.4 + - name: Set up ORAS + uses: oras-project/setup-oras@22ce207df3b08e061f537244349aac6ae1d214f6 # v1.2.4 + with: + version: 1.3.0 - name: Generate SHA-256 checksum run: | @@ -54,16 +56,45 @@ jobs: with: subject-path: packaged/topograph-*.tgz - - name: Publish Helm chart to NGC + - name: Log into NGC OCI registry env: - HELM_REPO_USERNAME: $oauthtoken - HELM_REPO_PASSWORD: ${{ secrets.NGC_API_KEY }} + HELM_REGISTRY_CONFIG: ${{ runner.temp }}/helm-release-registry.json + NGC_API_KEY: ${{ secrets.NGC_API_KEY }} run: | set -euo pipefail - if [[ -z "${HELM_REPO_PASSWORD}" ]]; then + if [[ -z "${NGC_API_KEY}" ]]; then echo "::error::The NGC_API_KEY secret is required to publish Helm charts." exit 1 fi - for chart in packaged/topograph-*.tgz; do - helm cm-push "${chart}" "${HELM_REPOSITORY}" + printf '%s' "${NGC_API_KEY}" | helm registry login "${HELM_REPOSITORY%%/*}" \ + --username '$oauthtoken' --password-stdin + + - name: Publish Helm chart and checksum to NGC OCI registry + env: + HELM_REGISTRY_CONFIG: ${{ runner.temp }}/helm-release-registry.json + run: | + set -euo pipefail + cd packaged + for chart in topograph-*.tgz; do + sha256sum --check "${chart}.sha256" + if ! output=$(helm push "${chart}" "oci://${HELM_REPOSITORY}" 2>&1); then + printf '%s\n' "${output}" + exit 1 + fi + printf '%s\n' "${output}" + digest=$(awk '$1 == "Digest:" {print $2}' <<< "${output}") + if [[ ! "${digest}" =~ ^sha256:[a-f0-9]{64}$ ]]; then + echo "::error::Helm did not return a valid chart manifest digest." + exit 1 + fi + oras attach --registry-config "${HELM_REGISTRY_CONFIG}" \ + --distribution-spec v1.1-referrers-api \ + --artifact-type application/vnd.topograph.checksum.v1 \ + "${HELM_REPOSITORY}/topograph@${digest}" "${chart}.sha256:text/plain" done + + - name: Remove registry credentials + if: always() + env: + HELM_REGISTRY_CONFIG: ${{ runner.temp }}/helm-release-registry.json + run: rm -f "${HELM_REGISTRY_CONFIG}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 4ada84c7..00000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,164 +0,0 @@ -name: Release - -on: - push: - tags: - - "v*" - workflow_dispatch: - inputs: - tag: - description: Existing release tag (vX.Y.Z) - required: true - type: string - -concurrency: - group: helm-repository-publication - cancel-in-progress: false - -permissions: {} - -jobs: - release: - name: Publish release - runs-on: linux-amd64-cpu8 - permissions: - attestations: write - contents: write - id-token: write - - steps: - - name: Checkout release tag - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - fetch-depth: 0 - persist-credentials: false - ref: ${{ inputs.tag || github.ref }} - - - name: Validate release metadata - id: release - env: - RELEASE_TAG: ${{ inputs.tag || github.ref_name }} - run: | - set -euo pipefail - - if [[ ! "${RELEASE_TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "::error::Release tag must match vX.Y.Z; got ${RELEASE_TAG}" - exit 1 - fi - - if [[ "${GITHUB_REF}" != "refs/tags/${RELEASE_TAG}" ]]; then - echo "::error::Run this workflow from tag ${RELEASE_TAG} so attestations identify the canonical tag." - echo "::error::Retry with: gh workflow run release.yml --ref ${RELEASE_TAG} -f tag=${RELEASE_TAG}" - exit 1 - fi - - tag_commit=$(git rev-parse "${RELEASE_TAG}^{commit}") - checkout_commit=$(git rev-parse HEAD) - if [[ "${tag_commit}" != "${checkout_commit}" ]]; then - echo "::error::Tag ${RELEASE_TAG} resolves to ${tag_commit}, but checkout is ${checkout_commit}." - exit 1 - fi - - version=${RELEASE_TAG#v} - chart_version=$(awk '$1 == "version:" {gsub(/"/, "", $2); print $2; exit}' charts/topograph/Chart.yaml) - app_version=$(awk '$1 == "appVersion:" {gsub(/"/, "", $2); print $2; exit}' charts/topograph/Chart.yaml) - - if [[ "${chart_version}" != "${version}" ]]; then - echo "::error::Chart version ${chart_version} does not match release version ${version}." - exit 1 - fi - if [[ "${app_version}" != "${RELEASE_TAG}" ]]; then - echo "::error::Chart appVersion ${app_version} does not match release tag ${RELEASE_TAG}." - exit 1 - fi - - awk -v heading="## [${version}]" ' - index($0, heading) == 1 && (length($0) == length(heading) || substr($0, length(heading) + 1, 3) == " - ") { - found = 1 - next - } - found && /^## / { exit } - found { print } - END { if (!found) exit 1 } - ' CHANGELOG.md > release-notes.md - - if [[ ! -s release-notes.md ]] || ! grep -q '[^[:space:]]' release-notes.md; then - echo "::error::CHANGELOG.md has no release notes for ${version}." - exit 1 - fi - - echo "tag=${RELEASE_TAG}" >> "${GITHUB_OUTPUT}" - echo "version=${version}" >> "${GITHUB_OUTPUT}" - - - name: Set up Helm - uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1 - with: - version: v3.14.0 - - - name: Package Helm chart - run: | - mkdir -p packaged - helm lint charts/topograph - helm package charts/topograph --destination packaged - - - name: Generate SHA-256 checksum - env: - CHART: packaged/topograph-${{ steps.release.outputs.version }}.tgz - run: | - set -euo pipefail - cd "$(dirname "${CHART}")" - chart=$(basename "${CHART}") - sha256sum "${chart}" > "${chart}.sha256" - sha256sum --check "${chart}.sha256" - - - name: Generate SLSA provenance - uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2 - with: - subject-path: packaged/topograph-${{ steps.release.outputs.version }}.tgz - - - name: Fetch existing Helm repository index - run: | - set -euo pipefail - if ! remote_ref=$(git ls-remote --heads origin refs/heads/gh-pages); then - echo "::error::Failed to check for the existing gh-pages branch." - exit 1 - fi - if [[ -n "${remote_ref}" ]]; then - git fetch origin refs/heads/gh-pages:refs/remotes/origin/gh-pages - git checkout origin/gh-pages -- index.yaml - fi - - - name: Generate Helm repository index - # Pass context values through env rather than expanding ${{ }} inside - # the script, so the shell never parses them as code. - env: - REPO_OWNER: ${{ github.repository_owner }} - REPO_NAME: ${{ github.event.repository.name }} - run: | - if [[ -f index.yaml ]]; then - helm repo index packaged \ - --url "https://${REPO_OWNER}.github.io/${REPO_NAME}" \ - --merge index.yaml - else - helm repo index packaged \ - --url "https://${REPO_OWNER}.github.io/${REPO_NAME}" - fi - - - name: Deploy Helm repository to GitHub Pages - uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4.1.0 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - keep_files: true - publish_branch: gh-pages - publish_dir: ./packaged - - - name: Create GitHub release - uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3 - with: - body_path: release-notes.md - files: | - packaged/topograph-${{ steps.release.outputs.version }}.tgz - packaged/topograph-${{ steps.release.outputs.version }}.tgz.sha256 - prerelease: false - tag_name: ${{ steps.release.outputs.tag }} - name: ${{ steps.release.outputs.tag }} diff --git a/CHANGELOG.md b/CHANGELOG.md index bdf17f1e..98eed07a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,9 +12,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - Firebird obtains one fresh access token per topology generation request and reuses it across all pages, assuming it remains valid for the request duration. Tokens are no longer cached across requests, and HTTP 401 does not trigger reauthentication. -- Manual Helm chart publishing now targets the NGC `dsx-ai-factory-staging/topograph` team instead of GitHub Pages. -- Container image publishing now targets `nvcr.io/dsx-ai-factory-staging/topograph/topograph` in NGC using the `NGC_API_KEY` GitHub Actions secret. -- **BREAKING:** The Go module and canonical repository moved from `github.com/NVIDIA/topograph` to `github.com/dsx-ai-factory/topograph`. Repository links, published container and Helm chart locations, build metadata, and examples now use the `dsx-ai-factory` organization. +- Manual Helm chart publishing now targets the NGC OCI repository `nvcr.io/nvidia/topograph/topograph` instead of GitHub Pages. SHA-256 checksum files are attached to the chart manifest digest as OCI referrers. +- Container image publishing now targets `nvcr.io/nvidia/topograph/topograph`. +- **BREAKING:** The Go module and canonical repository moved from `github.com/NVIDIA/topograph` to `github.com/dsx-ai-factory/topograph`. - **BREAKING:** `topograph` and `node-observer` binaries now use [pflag](https://github.com/spf13/pflag) for flag parsing, matching `node-data-broker`. Single-dash long flags (`-version`, `-logtostderr`, `-vmodule`, etc.) now require a double dash (`--version`, `--logtostderr`, `--vmodule`). This affects the use of all [klog](https://github.com/kubernetes/klog) flags - **BREAKING:** `kwok-nodes` now uses [pflag](https://github.com/spf13/pflag) for flag parsing. Single-dash long flags (`-model`, `-output`, etc.) now require a double dash (`--model`, `--output`); the short forms `-m`, `-o`, `-g`, and `-p` are newly added aliases for `--model`, `--output`, `--gpus`, and `--pods` respectively. - Go toolchain upgraded from **1.26.6** to **1.27.3** across the module, container build, and CI workflows; the CI linter is upgraded to `golangci-lint` **v2.14.0** for Go 1.27 compatibility. @@ -38,10 +38,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - Firebird provider (`firebird`) discovers arbitrary-depth fabric paths from `GET /topology/nodes`, with token pagination, client-credentials authentication through `POST /auth/token` over a required HTTPS API URL, with one access token per topology generation request, leaf-first network paths, switch names taken directly from `networkPath`, `pageSize` and `pageToken` pagination query parameters, requested-node filtering, and structured diagnostics for Firebird HTTP 400, 401, 403, 404, and 429 errors. - `infiniband-sim` provider replays a saved `ibnetdiscover` output file through the production InfiniBand parser, allowing offline switch topology testing with requested-node filtering and no fabric access. -- SHA-256 checksum files published beside Helm chart packages and included - with their corresponding GitHub release assets. -- Tag-driven official releases that publish the Helm chart, checksum, build - provenance, and GitHub release from the canonical release commit. +- SHA-256 checksum files published as OCI referrers alongside Helm chart packages. +- Releases use a dedicated `vX.Y.Z` branch with final chart and changelog + metadata. The image and chart complete NVIDIA internal security review before + the approved artifacts are promoted unchanged to the public NGC repositories. - Signed SLSA build provenance for published container images and Helm chart packages. Release workflows bind each artifact to its source commit and workflow using GitHub artifact attestations; container provenance is also diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index f93fe1c2..6f1c2544 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -225,20 +225,36 @@ pushes for each PR; the first three also run on `main`: Release and maintenance workflows: -- `.github/workflows/docker.yml`: on a `v*` tag push or manual dispatch, - builds the `linux/amd64` and `linux/arm64` image, pushes it to GHCR, and +- `.github/workflows/docker.yml`: on manual dispatch, builds the + `linux/amd64` and `linux/arm64` image, uploads it to NVIDIA staging, and attests its provenance -- `.github/workflows/release.yml`: on a `v*` tag push, or a manual re-run - for an existing tag, checks the tag against `charts/topograph/Chart.yaml` - and `CHANGELOG.md`, packages and attests the Helm chart, updates the Helm - repository, and creates the GitHub release - `.github/workflows/publish-fern-docs.yml`: on a `vX.Y.Z` tag push or manual dispatch, publishes the versioned docs site -- `.github/workflows/helm-release.yaml`: manual Helm chart publication to - `gh-pages` +- `.github/workflows/helm-release.yaml`: manual Helm chart publication to NGC OCI, + with the `.tgz.sha256` file attached to the chart manifest digest using ORAS - `.github/workflows/scorecard.yaml`: OpenSSF Scorecard analysis on pushes to `main`, weekly, and on demand +The release workflow publishes +to `nvcr.io/1070195845945670/topograph/topograph`, using chart versions such as +`1.0.0` as tags (container release tags use the `v1.0.0` form). +After authenticating Helm and ORAS to `nvcr.io`, retrieve a chart and its checksum: + +```bash +chart_ref=nvcr.io/1070195845945670/topograph/topograph +chart_digest=$(oras resolve "${chart_ref}:1.0.0") +helm pull "oci://${chart_ref}@${chart_digest}" +checksum_digest=$(oras discover --format json \ + --artifact-type application/vnd.topograph.checksum.v1 \ + "${chart_ref}@${chart_digest}" | jq -er '.referrers[0].digest') +oras pull "${chart_ref}@${checksum_digest}" +sha256sum --check topograph-1.0.0.tgz.sha256 +``` + +The `.sha256` file is an OCI referrer retrieved with ORAS; `helm pull` downloads +the chart itself. Publication uses the existing `NGC_API_KEY` Actions secret and +requires push access to this OCI repository and its referrers. + Every `actions/setup-go` step reads the Go version from `go.mod`, so a toolchain bump there also moves the CI jobs that use it. The container image build does not: `Dockerfile` pins its own `golang` builder image, diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 3678bb3e..f047966a 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -41,10 +41,12 @@ flowchart LR | Vote on governance decisions | — | — | ✅ | tie-break only | | Cast deciding vote on ties | — | — | — | ✅ | | Final authority on decisions that affect IP | — | — | — | ✅ (for NVIDIA) | -| Cut releases | — | — | ✅ | ✅ | +| Cut releases | — | — | NVIDIA release manager only | NVIDIA release manager only | _* Project Leadership is an NVIDIA appointment, not a contributor ladder role. Current Project Leaders are listed in [MAINTAINERS.md](./MAINTAINERS.md)._ +For each release, the active maintainers nominate one active NVIDIA maintainer as release manager under [RELEASE.md](./RELEASE.md). Only the nominated release manager may cut that release. The role requires current NVIDIA employment and authorized access to NVIDIA's internal security review and release systems. Every release must complete NVIDIA's internal security review and approval process before public artifact publication. External maintainers cannot serve as release manager, but contributors and maintainers from any organization can help prepare release changes, review pull requests, and test publicly available candidates. If a replacement is needed, the maintainers nominate another eligible NVIDIA maintainer before publication continues. + A reviewer approval means technical acceptance. The code is correct, the approach is sound, and it meets the quality standards of the project. The maintainer who merges the PR verifies that every commit has DCO sign-off, required CI checks are passing, required approvals are present, and branch protection permits the merge. The maintainer does not review the code again. The two acts answer different questions, and two different people perform them. A reviewer can close a routine issue, which means a duplicate, an issue that is already fixed, an answered question, or a stale issue. A reviewer must not close an issue that reports an open bug or an unresolved feature request. Anyone can close an issue that they opened. @@ -146,7 +148,8 @@ Topograph separates decisions into two layers. The layer a decision belongs to d - Reviewing, approving, and merging pull requests - Triaging, labeling, and closing issues -- Cutting a release under [RELEASE.md](./RELEASE.md), including which ready work lands in a given release +- Preparing a release, including which ready work lands in a given release +- Cutting a release under [RELEASE.md](./RELEASE.md), restricted to the nominated NVIDIA release manager after the required internal security review and approval - Bug fixes, dependency version bumps, documentation, tests, and CI changes - Adding a parameter, label value, or configuration field that leaves every existing contract intact diff --git a/README.md b/README.md index 2f9afc38..dedbe389 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ Topograph is a component that discovers the physical network topology of a clust Requires Kubernetes 1.27 or later, Helm 3.10+ or 4.x, `kubectl` with permission to install a chart and create a namespace, and credentials for whichever provider matches your environment. ```bash -helm repo add topograph https://dsx-ai-factory.github.io/topograph +helm repo add topograph https://helm.ngc.nvidia.com/nvidia/topograph helm repo update helm install topograph topograph/topograph \ @@ -149,7 +149,7 @@ Topograph sits between topology sources and schedulers, and replaces neither. | Artifact | Where it comes from | |---|---| | Container image | `nvcr.io/nvidia/topograph/topograph` | -| Helm chart | [`charts/topograph/`](charts/topograph/) in this repository, published to the chart repository at `https://dsx-ai-factory.github.io/topograph` | +| Helm chart | [`charts/topograph/`](charts/topograph/) in this repository, published to the chart repository at `https://helm.ngc.nvidia.com/nvidia/topograph` | | `.deb` package | `make deb`, written to `bin/` | | `.rpm` package | `make rpm`, written to `bin/` | diff --git a/RELEASE.md b/RELEASE.md index 2942718d..0dca9554 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,6 +1,8 @@ # Release Process -This document outlines the release process for Topograph. +This document describes how Topograph releases are prepared and published. +Container images, Helm charts, and other release artifacts must complete +NVIDIA's internal security review and approval process before public release. ## Release cadence and responsibility @@ -8,231 +10,185 @@ Topograph targets one minor release per calendar quarter. The cadence is time-based: work that is not ready for a scheduled release moves to a later release rather than delaying the release solely to include it. -For each release, the active maintainers nominate one active maintainer to -serve as the release person. The nomination should be recorded in the release -tracking issue or another public project record before publication begins. +For each release, the active maintainers nominate one active NVIDIA maintainer +to serve as the release manager. Record the nomination in the release tracking +issue or another public project record before publication begins. -Only the nominated release person may initiate that release by running the -publication workflows and publishing the GitHub release. If the release person -cannot complete the release, the maintainers nominate a replacement before -another person initiates it. +**At this moment, only NVIDIA employees with the required internal access may serve as release +manager or cut a release.** The role requires access to NVIDIA's internal +security review and release systems. GitHub maintainer or repository write +access alone is insufficient. External contributors, including external +maintainers, cannot serve as release manager. -The active maintainer roster is maintained in [MAINTAINERS.md](./MAINTAINERS.md), -and maintainer release authority is defined in [GOVERNANCE.md](./GOVERNANCE.md). - -## Prerequisites - -- Nomination as the release person for the release -- Repository write access, including access to GitHub Actions and GitHub Releases -- Understanding of semantic versioning (`vMAJOR.MINOR.PATCH`) -- A clean release commit with all required CI checks passing +The active maintainer roster is maintained in [MAINTAINERS.md](./MAINTAINERS.md). +The release manager eligibility requirement in this document applies in +addition to the project roles described in [GOVERNANCE.md](./GOVERNANCE.md). ## Version management -- Official versions use `vX.Y.Z`, where `X`, `Y`, and `Z` are the major, - minor, and patch versions. -- Each minor release line has one long-lived branch named `release-X.Y`, such - as `release-1.2`. Release candidates, the `vX.Y.0` release, and all subsequent - `vX.Y.Z` patch releases are prepared from that branch. -- Git tags and GitHub releases use the canonical version, such as `v1.2.0` or - `v1.2.1`; release branches do not include the patch version. -- The Helm chart `version` omits the `v` prefix, while `appVersion` includes it. - For example, release `v1.2.0` uses `version: "1.2.0"` and +- Each release, including every patch release, has a dedicated branch named + `vX.Y.Z`, such as `v1.2.0` or `v1.2.1`. Development continues on `main`. +- Prepare the final release metadata on that branch before building artifacts. + In `charts/topograph/Chart.yaml`, set `version` to `X.Y.Z` and `appVersion` + to `vX.Y.Z`. For example, `v1.2.0` uses `version: "1.2.0"` and `appVersion: "v1.2.0"`. -- Release candidates increment the candidate suffix in the Helm chart version, - such as `1.2.0-rc.1` and `1.2.0-rc.2`, while remaining on `release-1.2`. - -This follows the -[Kubernetes release-branch model](https://kubernetes.io/releases/release/): -development continues on `main`, while a `release-X.Y` branch is retained for -stabilization and patch releases. +- Prepare the corresponding dated release section and comparison links in + `CHANGELOG.md`, leaving an **Unreleased** section for future changes. +- Build and review the final version through one cycle. The approved image + and chart are promoted to the public repositories without rebuilding. +- After approval, create an annotated tag named `vX.Y.Z` at the exact source + commit used for the approved artifacts. The branch and tag share a name; + use explicit `refs/heads/` and `refs/tags/` references to distinguish them. ## Release procedure -### Create and maintain the release branch - -Create the release branch from `main` when the changes intended for the first -release candidate have merged. A branch is created only once for a minor -release line; patch releases reuse it. - -```bash -git switch main -git pull --ff-only origin main -git switch -c release-1.2 -git push -u origin release-1.2 -``` - -After the branch is created: - -- New feature development continues on `main`. -- Bug fixes, test fixes, and documentation changes that also apply to future - releases merge into `main` first, then are cherry-picked through a reviewed - pull request targeting `release-1.2`. -- Release-specific changes, such as chart and changelog preparation, may be - submitted directly as a pull request targeting `release-1.2`. -- Do not rebase or force-push the shared release branch, and do not merge the - release branch back into `main`. - -### Prerelease cycle - -Prepare every release candidate on the release branch. +### Prepare the release branch -1. Confirm that every change intended for the candidate is present on - `release-1.2` and that required CI checks pass. +1. Create a dedicated branch from the intended source commit. For a release + from `main`: -2. Create a preparation branch from `release-1.2`. In - `charts/topograph/Chart.yaml`, set `version` to the release-candidate - version. Omit the `v` prefix and include the candidate number, such as - `1.2.0-rc.1`. Leave `appVersion` unchanged for now. - - Run the [quality gates](#quality-gates), commit with DCO sign-off, and open a - pull request targeting `release-1.2`. Merge it after review and required CI - checks pass. + ```bash + git switch main + git pull --ff-only origin main + git switch -c v1.2.0 + git push -u origin refs/heads/v1.2.0:refs/heads/v1.2.0 + ``` -3. In GitHub, run the **Docker** workflow against `release-1.2`. The workflow - publishes the Topograph container image with two tags: the release branch - name and the short commit SHA. It also generates signed - SLSA provenance for the image and publishes it to GHCR. + Each patch release gets its own branch. When preparing a patch from an + earlier release, select that release's explicit tag reference as the base + and include the intended fixes through reviewed pull requests. -4. Set `appVersion` in `charts/topograph/Chart.yaml` to the short commit SHA - produced in the previous step. Submit this change through another reviewed - pull request targeting `release-1.2` and merge it after required CI checks - pass. +2. Submit a preparation pull request targeting `v1.2.0` that updates + `charts/topograph/Chart.yaml`: -5. Run the **Release Helm Charts** workflow against `release-1.2`. The - workflow publishes the chart package to the Topograph Helm repository. For - example, version `1.2.0-rc.1` is published as: + ```yaml + version: "1.2.0" + appVersion: "v1.2.0" + ``` - `https://dsx-ai-factory.github.io/topograph/topograph-1.2.0-rc.1.tgz` + Confirm that the chart's image repository is + `nvcr.io/nvidia/topograph/topograph`. Keeping the final public image address + in the chart allows the approved package to be promoted unchanged. - The workflow also publishes - `topograph-1.2.0-rc.1.tgz.sha256` beside the chart package and generates - signed SLSA provenance for the chart package. +3. In the same pull request, consolidate the release notes in `CHANGELOG.md`, + move the relevant **Unreleased** entries into `## [1.2.0] - YYYY-MM-DD`, and + update the comparison links. Leave an **Unreleased** section for future work. -6. Run the release-candidate test cycle. +4. Run the [quality gates](#quality-gates) applicable before publication, + commit with DCO sign-off, and merge after review and required CI checks pass. + Record the exact release branch commit SHA used for the build. - If testing reveals that generally applicable changes are needed, merge them - into `main` and cherry-pick them through a pull request to `release-1.2`. - When the changes for the next candidate are ready, increment the candidate - number in `Chart.yaml` (for example, from `1.2.0-rc.1` to - `1.2.0-rc.2`) and repeat steps 1 through 6. +New feature development continues on `main`. Generally applicable fixes merge +there first and are cherry-picked through reviewed pull requests to the release +branch. Do not rebase or force-push the shared release branch. -### Official release +### Build and complete internal security review -Use these steps for both the initial `vX.Y.0` release and later `vX.Y.Z` patch -releases. The release branch must already exist; do not create a version-named -branch for an official release. +1. Build the multi-architecture image and Helm chart from the recorded commit + on `v1.2.0`, using the final version metadata. The manual + [Docker workflow](./.github/workflows/docker.yml) and + [Helm workflow](./.github/workflows/helm-release.yaml) upload artifacts to + the configured NVIDIA staging repositories. Confirm their destinations + are restricted to staging before running them. Keep the branch at the + recorded commit while both builds run, and verify both builds used it. -1. Confirm that every change intended for the release is present on the - corresponding release branch and that required CI checks pass. For example, - release `v1.2.0` and patch release `v1.2.1` both come from `release-1.2`. +2. Record the source commit, staging image reference and manifest digest, + chart version and SHA-256 checksum, and signed build provenance. Test the + staged artifacts using NVIDIA's internal release instructions. If testing + needs an image override to access staging, apply it at deployment time; + preserve the chart package intended for promotion. -2. Create a preparation branch from `release-1.2` and update - `charts/topograph/Chart.yaml`: +3. Submit the image and chart through NVIDIA's internal security system. + Follow the internal instructions for the required checks and approvals. + Record approval for the exact image digest and chart checksum. Pending or + failed checks block public promotion. - - Set `version` to the release version without the `v` prefix, such as - `1.2.0`. - - Set `appVersion` to the canonical release version, such as `v1.2.0`. +4. If a change is required, merge the correction through a reviewed pull + request to the same release branch, update the release notes as needed, + and repeat the build, testing, and internal security review for the changed + artifacts. Only the artifacts covered by the final approval may be promoted. -3. Prepare `CHANGELOG.md`: +### Promote and publish the release - - Consolidate the relevant entries and remove redundant intermediate - entries. - - Move the released changes from **Unreleased** to a section named - `## [1.2.0] - YYYY-MM-DD`. - - Keep an empty **Unreleased** section for future changes. - - Add or update the comparison link for the release. +1. Confirm that the branch still points to the source commit recorded for the + approved artifacts. Freeze that commit for publication. -4. Run the [quality gates](#quality-gates), commit the changes with DCO - sign-off, push the preparation branch, and create a pull request targeting - `release-1.2`. +2. Follow NVIDIA's internal promotion instructions to copy the approved image + to `nvcr.io/nvidia/topograph/topograph:v1.2.0` and the approved chart to + `https://helm.ngc.nvidia.com/nvidia/topograph`. Preserve the image manifest + digest and chart package bytes; do not rebuild, repackage, or edit either + artifact during promotion. Retain their checksums and provenance. -5. Merge the release pull request after review and required CI checks pass. Do - not publish official release artifacts from the branch name; the canonical - tag must identify the exact source commit used to build them. +3. Verify the public image digest and chart checksum against the approved + values using the commands in [release verification](#release-verification). -6. Resolve the current tip of `release-1.2`, then create and push an annotated - tag that targets that exact commit. First run the script without `--push` - and inspect the displayed commit: +4. Create the annotated source tag at the approved commit. First run: ```bash scripts/create-release-tag.sh v1.2.0 ``` - If validation succeeds and the commit is correct, re-run with `--push`. The - script repeats every validation before it creates and pushes the tag: + Compare the displayed commit with the approved source commit. Then run: ```bash scripts/create-release-tag.sh --push v1.2.0 ``` -7. The tag push automatically starts these workflows: + The helper resolves `refs/heads/v1.2.0` on `origin`; keep that branch frozen + through both commands. The resulting `refs/tags/v1.2.0` must identify the + source commit of the approved artifacts. Tagging does not rebuild them. + +5. Publish the GitHub release with the release notes and public NGC links. + Verify that any attached artifact copies match the approved files. The tag + triggers **Publish Fern Docs**; confirm versioned documentation publishes + successfully and complete the remaining release verification checks before + marking the release complete. - - **Release** packages and attests the Helm chart, generates and verifies its - SHA-256 checksum, publishes both files to the Helm repository, and creates - the GitHub release with both files attached. - - **Docker** publishes the multi-architecture container image and its signed - SLSA provenance. - - **Publish Fern Docs** publishes the versioned documentation. +6. Reconcile the dated changelog section and comparison links back to `main` + through a focused pull request, removing only the entries included in this + release from **Unreleased**. Preserve entries for later work. Do not merge + the release branch into `main`. -8. Complete the [release verification](#release-verification) after all three - workflows finish successfully. +## Security review and publication -9. Reconcile the released changelog metadata back to `main` through a focused - pull request. Copy the new dated release section and comparison link from - the release branch, and remove only the corresponding released entries from - **Unreleased** on `main`. Preserve entries added for the next release after - the release branch was created. Do not merge the release branch into - `main`. +The nominated NVIDIA release manager completes the internal security review, +records approval, and promotes the exact approved artifacts. A successful build +alone does not complete a release. Changes to an artifact require renewed review +and approval before promotion. -## Workflow pipeline +Keep internal reports and operational details in the appropriate NVIDIA internal +record. The public release record can summarize approval completion and identify +the source commit, public artifact references, digests, and checksums. + +## Release flow ```mermaid flowchart TD - A[main] -->|Cut at first RC| B[release-X.Y] - A --> C[Next-release development] - B --> D[RC preparation PR
X.Y.0-rc.N] - D --> E[Docker workflow
image and provenance] - E --> F[Set appVersion
to short SHA] - F --> G[Release Helm Charts
package and provenance] - G --> H[Release-candidate
test cycle] - H -->|Generally applicable fix| I[Merge fix into main] - I --> J[Cherry-pick PR
to release-X.Y] - J --> D - H -->|Ready for GA| K[GA preparation PR
to release-X.Y] - K --> L[Tag merged commit
vX.Y.0] - L --> M[Retain release-X.Y
for patch releases] - M -->|Approved backports| N[Patch preparation PR] - N --> O[Tag merged commit
vX.Y.Z] - L --> P[Release workflow
chart, checksum, provenance] - L --> Q[Docker workflow
image and provenance] - L --> R[Publish Fern Docs
versioned documentation] - O --> P - O --> Q - O --> R - P --> S[GitHub release] - S --> T[Reconcile CHANGELOG
to main] - T --> A + A[main or selected release commit] --> B[Dedicated vX.Y.Z branch] + B --> C[Update Chart.yaml and CHANGELOG.md] + C --> D[Review and quality gates] + D --> E[Build image and chart in staging] + E --> F[Test and submit to internal security system] + F -->|Changes required| C + F -->|Approved| G[Promote the same artifacts to public NGC] + G --> H[Verify public digests and checksums] + H --> I[Tag the approved source commit] + I --> J[Publish GitHub release and versioned docs] + J --> K[Reconcile CHANGELOG to main] ``` -The nominated release person manually dispatches **Docker** and **Release Helm -Charts** against `release-X.Y` for release candidates. Pushing an official -`vX.Y.Z` tag from that release line triggers the **Release**, **Docker**, and -**Publish Fern Docs** workflows. - ## Released components -An official release publishes: +An official release provides: -- A multi-architecture Topograph container image at - `ghcr.io/dsx-ai-factory/topograph:vX.Y.Z` -- A Helm chart in the Topograph chart repository at - `https://dsx-ai-factory.github.io/topograph` -- A SHA-256 checksum published beside the Helm chart package -- A GitHub release and source tag named `vX.Y.Z`, with the Helm chart and its - checksum attached +- A multi-architecture container image at + `nvcr.io/nvidia/topograph/topograph:vX.Y.Z` +- A Helm chart in the public NGC repository at + `https://helm.ngc.nvidia.com/nvidia/topograph` +- A GitHub release and source tag named `vX.Y.Z`, with release notes and links + to the public artifacts - Versioned documentation generated from the release tag -- Signed SLSA build provenance for the container image and Helm chart package +- Checksums and signed SLSA build provenance retained for artifact verification ## Quality gates @@ -243,91 +199,76 @@ All releases must pass: - Required GitHub CI checks on the release pull request - Review of `CHANGELOG.md` for complete, user-facing release notes - DCO sign-off verification for every commit -- Successful SLSA provenance generation for every published artifact +- Successful SLSA provenance generation for release artifacts +- Completion of NVIDIA's internal security review and approval process +- Verification that public artifacts match the artifacts approved for publication ## Release verification Verify the published container image: ```bash -docker buildx imagetools inspect ghcr.io/dsx-ai-factory/topograph:v1.2.0 +docker buildx imagetools inspect nvcr.io/nvidia/topograph/topograph:v1.2.0 ``` -Verify the published Helm chart: +Compare the published manifest digest with the image digest approved for release. + +Download and inspect the published Helm chart: ```bash -helm repo add topograph https://dsx-ai-factory.github.io/topograph +helm repo add topograph https://helm.ngc.nvidia.com/nvidia/topograph helm repo update helm show chart topograph/topograph --version 1.2.0 +helm pull topograph/topograph --version 1.2.0 +sha256sum topograph-1.2.0.tgz ``` -Verify the container image's SLSA provenance: +Compare the chart checksum with the checksum approved for release. Confirm that the chart's image repository and `appVersion` resolve +to the expected public image and release tag. -```bash -gh attestation verify oci://ghcr.io/dsx-ai-factory/topograph:v1.2.0 \ - --repo dsx-ai-factory/topograph \ - --signer-workflow dsx-ai-factory/topograph/.github/workflows/docker.yml \ - --source-ref refs/tags/v1.2.0 -``` - -Download the Helm chart and verify its SLSA provenance: - -```bash -curl -fsSLO https://dsx-ai-factory.github.io/topograph/topograph-1.2.0.tgz -curl -fsSLO https://dsx-ai-factory.github.io/topograph/topograph-1.2.0.tgz.sha256 -sha256sum --check topograph-1.2.0.tgz.sha256 -gh attestation verify topograph-1.2.0.tgz \ - --repo dsx-ai-factory/topograph \ - --signer-workflow dsx-ai-factory/topograph/.github/workflows/release.yml \ - --source-ref refs/tags/v1.2.0 -``` +Verify signed provenance against the approved artifact digests and source +commits, following the release verification instructions. Also confirm that: -- The [GitHub release](https://github.com/dsx-ai-factory/topograph/releases) has the - correct tag, release notes, Helm chart, and checksum. -- The **Docker**, **Release**, and **Publish Fern Docs** workflow - runs completed successfully. -- The published chart references the expected container image tag. +- Internal security review and approvals cover the published artifacts. +- The [GitHub release](https://github.com/dsx-ai-factory/topograph/releases) has + the correct tag, release notes, and public NGC links. +- Any attached artifact copies match the approved artifacts. +- Artifact publication and **Publish Fern Docs** completed successfully for + the intended source references. ## Troubleshooting -### Failed container or Helm publication +### Failed artifact publication -- Review the failed workflow's logs in GitHub Actions. -- Correct code or configuration through a reviewed pull request to `main`. -- Do not move or replace an existing release tag. If the release itself must be - corrected, prepare a new patch release. -- To retry the **Release** workflow without changing its provenance context, - dispatch it from the existing tag: +The release manager follows NVIDIA's internal release instructions to diagnose +and retry publication of the same approved artifacts. If a published image +digest or chart checksum differs from the approved artifact, stop the release +and resolve the discrepancy before continuing. - ```bash - gh workflow run release.yml --ref v1.2.0 -f tag=v1.2.0 - ``` +### Failed or pending security checks -- To retry the **Docker** workflow, dispatch it from the same existing tag: +Do not publish the affected artifacts. Follow NVIDIA's internal process to +resolve findings and obtain the required approvals. Handle security-sensitive +findings through [SECURITY.md](./SECURITY.md); do not post internal reports or +vulnerability details in public issues or release notes. - ```bash - gh workflow run docker.yml --ref v1.2.0 - ``` +If a fix changes an artifact, prepare it through a reviewed pull request and +repeat the build, security review, and approval for the corrected version. +Before publication, repeat this cycle on the same release branch. After +publication, prepare a new patch release on its own version branch. Never move +or replace an existing release tag or overwrite published artifacts. ### Failed documentation publication -- Review the **Publish Fern Docs** workflow logs. -- Confirm that the tag exists and matches `vX.Y.Z`. -- After correcting the failure, manually dispatch **Publish Fern Docs** with - the existing tag. Do not create a replacement tag solely to retry the docs - publication. +Review the **Publish Fern Docs** workflow logs and confirm that the tag exists +and matches `vX.Y.Z`. After correcting the failure, manually dispatch the +workflow with the existing tag. Do not create a replacement tag solely to retry +documentation publication. ### Failed provenance generation -- Review the **Generate SLSA provenance** step in the publishing workflow. -- For container provenance, confirm that the Docker job grants the following: - `packages: write`, `id-token: write`, `attestations: write`, and - `artifact-metadata: write`. -- For Helm provenance, confirm that the Helm job grants `id-token: write` and - `attestations: write`. It does not need `artifact-metadata: write` because the - chart attestation is stored by GitHub rather than pushed to an OCI registry. -- Retry the failed publishing workflow from the same tag. For **Release**, use - the tag-bound manual dispatch command shown above so the new attestation still - names `refs/tags/v1.2.0` as its source. +Review the **Generate SLSA provenance** step in the publishing workflow and its +required permissions. Retry from the same source reference and retain the +provenance for the artifact approved for publication. Missing provenance must be resolved before completing the release. diff --git a/SECURITY.md b/SECURITY.md index 371b75df..60aee41d 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -70,32 +70,36 @@ expected for those releases, not a supply chain problem. An official release publishes a multi-architecture container image at `nvcr.io/nvidia/topograph/topograph:vX.Y.Z`, a Helm chart package in the chart repository at -`https://dsx-ai-factory.github.io/topograph`, and a SHA-256 checksum file beside the chart package. The +`https://helm.ngc.nvidia.com/nvidia/topograph`, and a SHA-256 checksum file beside the chart package. The chart package and its checksum are also attached to the GitHub release. The image and the chart package each carry SLSA build provenance produced by GitHub artifact attestations, which are signed through Sigstore. Verify what you downloaded before installing it, substituting the release you are checking for `vX.Y.Z` and `X.Y.Z`. +Artifacts are built from the dedicated `vX.Y.Z` branch and promoted unchanged +after internal approval. Their provenance identifies `refs/heads/vX.Y.Z`; +the release tag subsequently records the same approved source commit. + Container image: ```bash gh attestation verify oci://nvcr.io/nvidia/topograph/topograph:vX.Y.Z \ --repo dsx-ai-factory/topograph \ --signer-workflow dsx-ai-factory/topograph/.github/workflows/docker.yml \ - --source-ref refs/tags/vX.Y.Z + --source-ref refs/heads/vX.Y.Z ``` Helm chart package and its checksum: ```bash -curl -fsSLO https://dsx-ai-factory.github.io/topograph/topograph-X.Y.Z.tgz -curl -fsSLO https://dsx-ai-factory.github.io/topograph/topograph-X.Y.Z.tgz.sha256 +helm pull topograph --repo https://helm.ngc.nvidia.com/nvidia/topograph --version X.Y.Z +curl -fsSLO https://helm.ngc.nvidia.com/nvidia/topograph/topograph-X.Y.Z.tgz.sha256 sha256sum --check topograph-X.Y.Z.tgz.sha256 gh attestation verify topograph-X.Y.Z.tgz \ --repo dsx-ai-factory/topograph \ - --signer-workflow dsx-ai-factory/topograph/.github/workflows/release.yml \ - --source-ref refs/tags/vX.Y.Z + --signer-workflow dsx-ai-factory/topograph/.github/workflows/helm-release.yaml \ + --source-ref refs/heads/vX.Y.Z ``` For a release covered by this section, treat a failed verification, a missing attestation, or an diff --git a/charts/topograph/README.md b/charts/topograph/README.md index 82ded4f9..8a60e210 100644 --- a/charts/topograph/README.md +++ b/charts/topograph/README.md @@ -15,7 +15,7 @@ Topograph discovers the physical network topology of a cluster (NVLink domains, From the Helm chart repository: ```bash -helm repo add topograph https://dsx-ai-factory.github.io/topograph +helm repo add topograph https://helm.ngc.nvidia.com/nvidia/topograph helm repo update helm install topograph \ topograph/topograph \ diff --git a/docs/engines/k8s.md b/docs/engines/k8s.md index a005da12..5bb391dc 100644 --- a/docs/engines/k8s.md +++ b/docs/engines/k8s.md @@ -506,7 +506,7 @@ The chart ships two `helm test` hook pods (`charts/topograph/templates/tests/`) Run the suite after installation: ```bash -helm repo add topograph https://dsx-ai-factory.github.io/topograph +helm repo add topograph https://helm.ngc.nvidia.com/nvidia/topograph helm repo update helm install topograph topograph/topograph \ --namespace topograph --create-namespace @@ -526,7 +526,7 @@ Both pods clean themselves up on success (`helm.sh/hook-delete-policy: before-ho The test pods do not mount a service account token (`automountServiceAccountToken: false`) and set small CPU and memory requests and limits (requests `10m` / `16Mi`, limits `100m` / `128Mi`), so the pods are admitted in namespaces whose `ResourceQuota` requires requests and limits. If a namespace `LimitRange` sets a minimum or a `maxLimitRequestRatio`, adjust the values to fit it. Override the defaults with `tests.resources`. -**Air-gapped environments.** The test pods reuse the main topograph image by default — they invoke `busybox wget` from the Alpine-based `ghcr.io/dsx-ai-factory/topograph` image already pulled by the Deployment. No additional image pull is required by `helm test`, so the suite works in environments where only mirrored images are reachable. If your mirrored image lacks `busybox wget`, override the test image: +**Air-gapped environments.** The test pods reuse the main topograph image by default — they invoke `busybox wget` from the Alpine-based `nvcr.io/nvidia/topograph/topograph` image already pulled by the Deployment. No additional image pull is required by `helm test`, so the suite works in environments where only mirrored images are reachable. If your mirrored image lacks `busybox wget`, override the test image: ```yaml tests: diff --git a/docs/get-started/quickstart-k8s.md b/docs/get-started/quickstart-k8s.md index a7545de8..63c0c7ff 100644 --- a/docs/get-started/quickstart-k8s.md +++ b/docs/get-started/quickstart-k8s.md @@ -28,7 +28,7 @@ produced. Base install command (pick the engine-specific flags from the two sections below): ```bash -helm repo add topograph https://dsx-ai-factory.github.io/topograph +helm repo add topograph https://helm.ngc.nvidia.com/nvidia/topograph helm repo update helm install topograph topograph/topograph \ --version \ diff --git a/docs/providers/lambdai.md b/docs/providers/lambdai.md index f1d1bebd..9b3f3818 100644 --- a/docs/providers/lambdai.md +++ b/docs/providers/lambdai.md @@ -107,7 +107,8 @@ serviceAccount: ### 3. Install with Helm (no credentials Secret) ```bash -helm install topograph oci://ghcr.io/dsx-ai-factory/topograph/topograph \ +helm install --wait topograph topograph \ + --repo https://helm.ngc.nvidia.com/nvidia/topograph \ --version -n topograph --create-namespace \ -f values.k8s.lambdai-workload-identity-example.yaml ``` @@ -260,7 +261,8 @@ Install with Helm (see the [Kubernetes quickstart](../get-started/quickstart-k8s kubectl create secret generic lambdai-creds \ --from-file=credentials.yaml=creds.yaml -n topograph -helm install topograph oci://ghcr.io/dsx-ai-factory/topograph/topograph \ +helm install --wait topograph topograph \ + --repo https://helm.ngc.nvidia.com/nvidia/topograph \ --version -n topograph --create-namespace \ --set provider.name=lambdai \ --set provider.params.url=https://cloud.example.com \ diff --git a/scripts/create-release-tag.sh b/scripts/create-release-tag.sh index e8be91cb..40faafa4 100755 --- a/scripts/create-release-tag.sh +++ b/scripts/create-release-tag.sh @@ -9,7 +9,7 @@ usage() { cat < -Resolve the current tip of the matching release-X.Y branch. By default, the +Resolve the current tip of the matching vX.Y.Z branch. By default, the script only validates and displays the commit. With --push, it creates and pushes the annotated release tag. @@ -49,7 +49,7 @@ if [[ ! "${release_tag}" =~ ^v([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then exit 2 fi -release_branch="release-${BASH_REMATCH[1]}.${BASH_REMATCH[2]}" +release_branch="${release_tag}" if ! command -v git >/dev/null 2>&1; then echo "required command not found: git" >&2 @@ -74,7 +74,7 @@ fi local_tag_exists=false if git rev-parse --verify --quiet "refs/tags/${release_tag}" >/dev/null; then - local_tag_commit=$(git rev-parse "${release_tag}^{commit}") + local_tag_commit=$(git rev-parse "refs/tags/${release_tag}^{commit}") local_tag_type=$(git cat-file -t "refs/tags/${release_tag}") if [[ "${local_tag_commit}" != "${release_head}" ]]; then echo "local tag ${release_tag} targets ${local_tag_commit}, not ${release_head}" >&2