From 213885634b23245ac2ee87fd4f41991d50c80489 Mon Sep 17 00:00:00 2001 From: Swarom Muley Date: Thu, 16 Jul 2026 22:19:13 -0700 Subject: [PATCH 1/4] fix: format release container references Signed-off-by: Swarom Muley --- .../tests/test_release_slack_notification.py | 157 ++++++++++++++++++ .github/workflows/release.yaml | 12 +- 2 files changed, 165 insertions(+), 4 deletions(-) create mode 100644 .github/scripts/tests/test_release_slack_notification.py diff --git a/.github/scripts/tests/test_release_slack_notification.py b/.github/scripts/tests/test_release_slack_notification.py new file mode 100644 index 0000000000..ecd37d90d3 --- /dev/null +++ b/.github/scripts/tests/test_release_slack_notification.py @@ -0,0 +1,157 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +import json +import os +import subprocess +from pathlib import Path + +import pytest +import yaml + +REPO_ROOT = Path(__file__).parents[3] +RELEASE_WORKFLOW = REPO_ROOT / ".github" / "workflows" / "release.yaml" +NODE_BINARY = os.environ.get("NODE_BINARY", "node") +NIGHTLY_REGISTRY = "ghcr.io/nvidia-nemo/nemo-platform" +STABLE_REGISTRY = "nvcr.io/nvidia/nemo-platform" + + +def _notify_end_step() -> dict: + workflow = yaml.safe_load(RELEASE_WORKFLOW.read_text()) + return next(step for step in workflow["jobs"]["notify-end"]["steps"] if step["name"] == "Send Slack alert") + + +def _run_notify_end_script(*, release_type: str, release_label: str, containers: list[str]) -> dict: + script = _notify_end_step()["with"]["script"] + runner = f""" +globalThis.core = {{setFailed(message) {{ throw new Error(message); }}}}; +globalThis.fetch = async (url, options) => {{ + process.stdout.write(JSON.stringify({{url, payload: JSON.parse(options.body)}})); + return {{ok: true, status: 200}}; +}}; +(async () => {{ +{script} +}})().catch((error) => {{ + console.error(error); + process.exitCode = 1; +}}); + """ + env = os.environ | { + "SLACK_ALERTS_WEBHOOK": "https://hooks.slack.test/alerts", + "SLACK_RELEASE_WEBHOOK": "https://hooks.slack.test/releases", + "RELEASE_TYPE": release_type, + "RELEASE_LABEL": release_label, + "SOURCE_SHA": "0123456789abcdef0123456789abcdef01234567", + "COMMIT_URL": ("https://github.com/NVIDIA-NeMo/nemo-platform/commit/0123456789abcdef0123456789abcdef01234567"), + "WHEEL_IDS": "[]", + "WHEEL_CATALOG": "[]", + "WHEEL_VERSION": "1.2.3", + "CONTAINER_IDS": json.dumps(containers), + "INCLUDE_HELM": "false", + "CHART_VERSION": "1.2.3", + "NIGHTLY_WHEEL_INDEX": "https://pypi.nvidia.com/nemo-platform-nightly/simple", + "STABLE_WHEEL_INDEX": "https://pypi.nvidia.com/nemo-platform/simple", + "NGC_CATALOG_BASE": "https://catalog.ngc.nvidia.com/orgs/nvidia/teams/nemo", + "RELEASE_NIGHTLY_CONTAINER_REGISTRY": NIGHTLY_REGISTRY, + "RELEASE_STABLE_CONTAINER_REGISTRY": STABLE_REGISTRY, + "POLL_RESULT": "success", + "GITHUB_RELEASE_RESULT": "success", + "DEPLOYMENT_RESULT": "success", + "RUN_URL": "https://github.com/NVIDIA-NeMo/nemo-platform/actions/runs/123", + "RUN_NUMBER": "456", + } + result = subprocess.run( + [NODE_BINARY, "-e", runner], + check=True, + capture_output=True, + env=env, + text=True, + ) + return json.loads(result.stdout) + + +def test_notify_end_step_passes_container_registry_constants() -> None: + step_env = _notify_end_step()["env"] + + assert step_env["RELEASE_NIGHTLY_CONTAINER_REGISTRY"] == ("${{ env.RELEASE_NIGHTLY_CONTAINER_REGISTRY }}") + assert step_env["RELEASE_STABLE_CONTAINER_REGISTRY"] == ("${{ env.RELEASE_STABLE_CONTAINER_REGISTRY }}") + + +@pytest.mark.parametrize( + ("release_type", "release_label", "containers", "title", "expected_refs"), + [ + ( + "nightly", + "nightly-20260716040726", + ["nmp-api", "nmp-cpu-tasks"], + "*:crescent_moon: Nightly release publish complete*", + [ + f"{NIGHTLY_REGISTRY}/nmp-api:nightly-20260716040726", + f"{NIGHTLY_REGISTRY}/nmp-cpu-tasks:nightly-20260716040726", + ], + ), + ( + "stable", + "1.2.3", + ["nmp-api"], + "*:ship: Release publish complete*", + [f"{STABLE_REGISTRY}/nmp-api:1.2.3"], + ), + ], +) +def test_notify_end_renders_copyable_container_refs( + release_type: str, + release_label: str, + containers: list[str], + title: str, + expected_refs: list[str], +) -> None: + request = _run_notify_end_script( + release_type=release_type, + release_label=release_label, + containers=containers, + ) + + expected_text = "\n".join( + [ + title, + f"Release: {release_label}", + "Commit: ", + "", + "*Artifacts published:*", + "*:docker_: Containers published:*", + "```", + *expected_refs, + "```", + "", + ":link: ", + ] + ) + assert request == { + "url": "https://hooks.slack.test/releases", + "payload": {"text": expected_text}, + } + + +def test_notify_end_omits_container_section_when_no_containers() -> None: + request = _run_notify_end_script( + release_type="nightly", + release_label="nightly-20260716040726", + containers=[], + ) + + assert request["payload"]["text"] == "\n".join( + [ + "*:crescent_moon: Nightly release publish complete*", + "Release: nightly-20260716040726", + "Commit: ", + "", + "*Artifacts published:*", + "", + ":link: ", + ] + ) + assert "Containers published" not in request["payload"]["text"] + assert "```" not in request["payload"]["text"] diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 6b893e4171..2ed595c376 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -1158,6 +1158,8 @@ jobs: WHEEL_CATALOG: ${{ env.RELEASE_WHEELS_JSON }} WHEEL_VERSION: ${{ needs.plan-release.outputs.wheel_version }} CONTAINER_IDS: ${{ needs.plan-release.outputs.container_ids }} + RELEASE_NIGHTLY_CONTAINER_REGISTRY: ${{ env.RELEASE_NIGHTLY_CONTAINER_REGISTRY }} + RELEASE_STABLE_CONTAINER_REGISTRY: ${{ env.RELEASE_STABLE_CONTAINER_REGISTRY }} INCLUDE_HELM: ${{ needs.plan-release.outputs.include_helm }} CHART_VERSION: ${{ needs.stage-helm.outputs.chart_version }} NIGHTLY_WHEEL_INDEX: ${{ env.RELEASE_NIGHTLY_WHEEL_INDEX }} @@ -1214,12 +1216,14 @@ jobs: } if (containerIds.length > 0) { lines.push("*:docker_: Containers published:*"); + lines.push("```"); + const containerRegistry = releaseType === "stable" + ? process.env.RELEASE_STABLE_CONTAINER_REGISTRY + : process.env.RELEASE_NIGHTLY_CONTAINER_REGISTRY; for (const containerId of containerIds) { - const container = releaseType === "stable" - ? `<${process.env.NGC_CATALOG_BASE}/containers/${containerId}|${containerId}>` - : containerId; - lines.push(`- ${container}: ${process.env.RELEASE_LABEL}`); + lines.push(`${containerRegistry}/${containerId}:${process.env.RELEASE_LABEL}`); } + lines.push("```"); } if (process.env.INCLUDE_HELM === "true") { const chart = releaseType === "stable" From 3198887d722dcccb730c12003ffc6aa73c400b4e Mon Sep 17 00:00:00 2001 From: Swarom Muley Date: Thu, 16 Jul 2026 22:29:04 -0700 Subject: [PATCH 2/4] test: remove notification regression test Signed-off-by: Swarom Muley --- .../tests/test_release_slack_notification.py | 157 ------------------ 1 file changed, 157 deletions(-) delete mode 100644 .github/scripts/tests/test_release_slack_notification.py diff --git a/.github/scripts/tests/test_release_slack_notification.py b/.github/scripts/tests/test_release_slack_notification.py deleted file mode 100644 index ecd37d90d3..0000000000 --- a/.github/scripts/tests/test_release_slack_notification.py +++ /dev/null @@ -1,157 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -import json -import os -import subprocess -from pathlib import Path - -import pytest -import yaml - -REPO_ROOT = Path(__file__).parents[3] -RELEASE_WORKFLOW = REPO_ROOT / ".github" / "workflows" / "release.yaml" -NODE_BINARY = os.environ.get("NODE_BINARY", "node") -NIGHTLY_REGISTRY = "ghcr.io/nvidia-nemo/nemo-platform" -STABLE_REGISTRY = "nvcr.io/nvidia/nemo-platform" - - -def _notify_end_step() -> dict: - workflow = yaml.safe_load(RELEASE_WORKFLOW.read_text()) - return next(step for step in workflow["jobs"]["notify-end"]["steps"] if step["name"] == "Send Slack alert") - - -def _run_notify_end_script(*, release_type: str, release_label: str, containers: list[str]) -> dict: - script = _notify_end_step()["with"]["script"] - runner = f""" -globalThis.core = {{setFailed(message) {{ throw new Error(message); }}}}; -globalThis.fetch = async (url, options) => {{ - process.stdout.write(JSON.stringify({{url, payload: JSON.parse(options.body)}})); - return {{ok: true, status: 200}}; -}}; -(async () => {{ -{script} -}})().catch((error) => {{ - console.error(error); - process.exitCode = 1; -}}); - """ - env = os.environ | { - "SLACK_ALERTS_WEBHOOK": "https://hooks.slack.test/alerts", - "SLACK_RELEASE_WEBHOOK": "https://hooks.slack.test/releases", - "RELEASE_TYPE": release_type, - "RELEASE_LABEL": release_label, - "SOURCE_SHA": "0123456789abcdef0123456789abcdef01234567", - "COMMIT_URL": ("https://github.com/NVIDIA-NeMo/nemo-platform/commit/0123456789abcdef0123456789abcdef01234567"), - "WHEEL_IDS": "[]", - "WHEEL_CATALOG": "[]", - "WHEEL_VERSION": "1.2.3", - "CONTAINER_IDS": json.dumps(containers), - "INCLUDE_HELM": "false", - "CHART_VERSION": "1.2.3", - "NIGHTLY_WHEEL_INDEX": "https://pypi.nvidia.com/nemo-platform-nightly/simple", - "STABLE_WHEEL_INDEX": "https://pypi.nvidia.com/nemo-platform/simple", - "NGC_CATALOG_BASE": "https://catalog.ngc.nvidia.com/orgs/nvidia/teams/nemo", - "RELEASE_NIGHTLY_CONTAINER_REGISTRY": NIGHTLY_REGISTRY, - "RELEASE_STABLE_CONTAINER_REGISTRY": STABLE_REGISTRY, - "POLL_RESULT": "success", - "GITHUB_RELEASE_RESULT": "success", - "DEPLOYMENT_RESULT": "success", - "RUN_URL": "https://github.com/NVIDIA-NeMo/nemo-platform/actions/runs/123", - "RUN_NUMBER": "456", - } - result = subprocess.run( - [NODE_BINARY, "-e", runner], - check=True, - capture_output=True, - env=env, - text=True, - ) - return json.loads(result.stdout) - - -def test_notify_end_step_passes_container_registry_constants() -> None: - step_env = _notify_end_step()["env"] - - assert step_env["RELEASE_NIGHTLY_CONTAINER_REGISTRY"] == ("${{ env.RELEASE_NIGHTLY_CONTAINER_REGISTRY }}") - assert step_env["RELEASE_STABLE_CONTAINER_REGISTRY"] == ("${{ env.RELEASE_STABLE_CONTAINER_REGISTRY }}") - - -@pytest.mark.parametrize( - ("release_type", "release_label", "containers", "title", "expected_refs"), - [ - ( - "nightly", - "nightly-20260716040726", - ["nmp-api", "nmp-cpu-tasks"], - "*:crescent_moon: Nightly release publish complete*", - [ - f"{NIGHTLY_REGISTRY}/nmp-api:nightly-20260716040726", - f"{NIGHTLY_REGISTRY}/nmp-cpu-tasks:nightly-20260716040726", - ], - ), - ( - "stable", - "1.2.3", - ["nmp-api"], - "*:ship: Release publish complete*", - [f"{STABLE_REGISTRY}/nmp-api:1.2.3"], - ), - ], -) -def test_notify_end_renders_copyable_container_refs( - release_type: str, - release_label: str, - containers: list[str], - title: str, - expected_refs: list[str], -) -> None: - request = _run_notify_end_script( - release_type=release_type, - release_label=release_label, - containers=containers, - ) - - expected_text = "\n".join( - [ - title, - f"Release: {release_label}", - "Commit: ", - "", - "*Artifacts published:*", - "*:docker_: Containers published:*", - "```", - *expected_refs, - "```", - "", - ":link: ", - ] - ) - assert request == { - "url": "https://hooks.slack.test/releases", - "payload": {"text": expected_text}, - } - - -def test_notify_end_omits_container_section_when_no_containers() -> None: - request = _run_notify_end_script( - release_type="nightly", - release_label="nightly-20260716040726", - containers=[], - ) - - assert request["payload"]["text"] == "\n".join( - [ - "*:crescent_moon: Nightly release publish complete*", - "Release: nightly-20260716040726", - "Commit: ", - "", - "*Artifacts published:*", - "", - ":link: ", - ] - ) - assert "Containers published" not in request["payload"]["text"] - assert "```" not in request["payload"]["text"] From 68b86e7b927baa8943424cdf3d7c0cbc1e8a53d7 Mon Sep 17 00:00:00 2001 From: Swarom Muley Date: Thu, 16 Jul 2026 22:34:53 -0700 Subject: [PATCH 3/4] fix: format all release artifact references Signed-off-by: Swarom Muley --- .github/workflows/release.yaml | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 2ed595c376..519d47a148 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -1162,9 +1162,6 @@ jobs: RELEASE_STABLE_CONTAINER_REGISTRY: ${{ env.RELEASE_STABLE_CONTAINER_REGISTRY }} INCLUDE_HELM: ${{ needs.plan-release.outputs.include_helm }} CHART_VERSION: ${{ needs.stage-helm.outputs.chart_version }} - NIGHTLY_WHEEL_INDEX: ${{ env.RELEASE_NIGHTLY_WHEEL_INDEX }} - STABLE_WHEEL_INDEX: ${{ env.RELEASE_STABLE_WHEEL_INDEX }} - NGC_CATALOG_BASE: ${{ env.RELEASE_NGC_CATALOG_BASE }} POLL_RESULT: ${{ needs.poll-final-release.result }} GITHUB_RELEASE_RESULT: ${{ needs.create-github-release.result }} DEPLOYMENT_RESULT: ${{ needs.signal-deployment.result }} @@ -1203,16 +1200,12 @@ jobs: lines.push("", "*Artifacts published:*"); if (wheelIds.length > 0) { lines.push("*:python: Wheels published:*"); + lines.push("```"); for (const wheelId of wheelIds) { const wheel = wheelCatalog.find((candidate) => candidate.id === wheelId); - const wheelIndex = releaseType === "nightly" - ? process.env.NIGHTLY_WHEEL_INDEX - : process.env.STABLE_WHEEL_INDEX; - const wheelUrl = releaseType === "nightly" - ? `${wheelIndex}/${wheel.package}/` - : `${wheelIndex.replace(/\/simple$/, "/project")}/${wheel.package}/${process.env.WHEEL_VERSION}/`; - lines.push(`- <${wheelUrl}|${wheel.package}: ${process.env.WHEEL_VERSION}>`); + lines.push(`${wheel.package}==${process.env.WHEEL_VERSION}`); } + lines.push("```"); } if (containerIds.length > 0) { lines.push("*:docker_: Containers published:*"); @@ -1226,11 +1219,10 @@ jobs: lines.push("```"); } if (process.env.INCLUDE_HELM === "true") { - const chart = releaseType === "stable" - ? `<${process.env.NGC_CATALOG_BASE}/helm-charts/nemo-platform|nemo-platform>` - : "nemo-platform"; lines.push("*:helm: Helm chart published:*"); - lines.push(`- ${chart}: ${process.env.CHART_VERSION}`); + lines.push("```"); + lines.push(`nemo-platform:${process.env.CHART_VERSION}`); + lines.push("```"); } } else { lines.push( From 8e41ceed9b26e80686ab0626cec7034ff772caca Mon Sep 17 00:00:00 2001 From: Swarom Muley Date: Fri, 17 Jul 2026 08:36:37 -0700 Subject: [PATCH 4/4] fix: show helm install commands Signed-off-by: Swarom Muley --- .github/workflows/release.yaml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 519d47a148..b8d8873b32 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -1160,6 +1160,8 @@ jobs: CONTAINER_IDS: ${{ needs.plan-release.outputs.container_ids }} RELEASE_NIGHTLY_CONTAINER_REGISTRY: ${{ env.RELEASE_NIGHTLY_CONTAINER_REGISTRY }} RELEASE_STABLE_CONTAINER_REGISTRY: ${{ env.RELEASE_STABLE_CONTAINER_REGISTRY }} + RELEASE_NIGHTLY_HELM_OCI_REGISTRY: ${{ env.RELEASE_NIGHTLY_HELM_OCI_REGISTRY }} + RELEASE_STABLE_HELM_REPOSITORY: ${{ env.RELEASE_STABLE_HELM_REPOSITORY }} INCLUDE_HELM: ${{ needs.plan-release.outputs.include_helm }} CHART_VERSION: ${{ needs.stage-helm.outputs.chart_version }} POLL_RESULT: ${{ needs.poll-final-release.result }} @@ -1221,7 +1223,10 @@ jobs: if (process.env.INCLUDE_HELM === "true") { lines.push("*:helm: Helm chart published:*"); lines.push("```"); - lines.push(`nemo-platform:${process.env.CHART_VERSION}`); + const helmInstall = releaseType === "stable" + ? `helm install nemo-platform nemo-platform --repo ${process.env.RELEASE_STABLE_HELM_REPOSITORY} --version ${process.env.CHART_VERSION}` + : `helm install nemo-platform ${process.env.RELEASE_NIGHTLY_HELM_OCI_REGISTRY}/nemo-platform --version ${process.env.CHART_VERSION}`; + lines.push(helmInstall); lines.push("```"); } } else {