From 2b1bc99184831861f78a767bda8d7a8c7cc56db0 Mon Sep 17 00:00:00 2001 From: Senthil Ravichandran Date: Wed, 19 Aug 2026 19:51:18 -0700 Subject: [PATCH 1/3] fix(e2e): pin Portable rootless Podman runtime Signed-off-by: Senthil Ravichandran --- .github/workflows/portable-profile-e2e.yaml | 39 +++++++++--- ci/source-shape-test-budget.json | 2 +- ...-profile-rootless-runtime-workflow.test.ts | 63 ++++++++++--------- 3 files changed, 65 insertions(+), 39 deletions(-) diff --git a/.github/workflows/portable-profile-e2e.yaml b/.github/workflows/portable-profile-e2e.yaml index 6046b783c7d..86d6667d3c9 100644 --- a/.github/workflows/portable-profile-e2e.yaml +++ b/.github/workflows/portable-profile-e2e.yaml @@ -37,8 +37,10 @@ concurrency: jobs: rootless-linux: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 timeout-minutes: 25 + env: + PODMAN_APT_VERSION: "5.7.0+ds2-3build1" steps: - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -70,14 +72,17 @@ jobs: run: | set -euo pipefail sudo apt-get update - sudo apt-get install --yes fuse-overlayfs passt podman slirp4netns uidmap - package_podman="/usr/bin/podman" - test -x "$package_podman" - runtime_bin="$(mktemp -d "${RUNNER_TEMP}/nemoclaw-rootless-runtime.XXXXXX")" - ln -s "$package_podman" "$runtime_bin/podman" - export PATH="$runtime_bin:$PATH" - printf '%s\n' "$runtime_bin" >> "$GITHUB_PATH" - test "$(readlink -f "$(command -v podman)")" = "$package_podman" + sudo apt-get install --yes \ + apparmor \ + fuse-overlayfs \ + passt \ + slirp4netns \ + uidmap \ + "podman=$PODMAN_APT_VERSION" + package_version="$(dpkg-query --show --showformat='${Version}' podman)" + version="$(podman --version)" + test "$package_version" = "$PODMAN_APT_VERSION" + test "$version" = "podman version 5.7.0" runtime_dir="/run/user/$(id -u)" sudo install -d -m 700 -o "$(id -u)" -g "$(id -g)" "$runtime_dir" if ! grep -q "^${USER}:" /etc/subuid; then @@ -90,6 +95,22 @@ jobs: pasta --version docker --version + - name: Apply Ubuntu pasta signal policy correction + shell: bash + run: | + set -euo pipefail + pasta_profile="/etc/apparmor.d/usr.bin.pasta" + signal_rule='^[[:space:]]*signal[[:space:]]+\(receive\)[[:space:]]+peer=podman,[[:space:]]*$' + test -f "$pasta_profile" + test "$(grep -Fc 'include ' "$pasta_profile")" -eq 1 + if ! grep -Eq "$signal_rule" "$pasta_profile"; then + sudo sed -i \ + '/^[[:space:]]*include [[:space:]]*$/a\ signal (receive) peer=podman,' \ + "$pasta_profile" + fi + test "$(grep -Ec "$signal_rule" "$pasta_profile")" -eq 1 + sudo apparmor_parser -r "$pasta_profile" + - name: Exercise portable profile in the rootless environment env: E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/portable-profile diff --git a/ci/source-shape-test-budget.json b/ci/source-shape-test-budget.json index fcd95d8c7bb..a489c44cff6 100644 --- a/ci/source-shape-test-budget.json +++ b/ci/source-shape-test-budget.json @@ -63,7 +63,7 @@ }, { "file": "test/e2e/support/portable-profile-rootless-runtime-workflow.test.ts", - "test": "compiles the managed inference catalogue after dependency installation and before the live E2E test (#9680)", + "test": "prepares Ubuntu 26.04, Podman 5.7, and the pasta AppArmor policy before the live E2E test (#9006)", "category": "compatibility" }, { diff --git a/test/e2e/support/portable-profile-rootless-runtime-workflow.test.ts b/test/e2e/support/portable-profile-rootless-runtime-workflow.test.ts index d502bbe3571..a358d07a3c5 100644 --- a/test/e2e/support/portable-profile-rootless-runtime-workflow.test.ts +++ b/test/e2e/support/portable-profile-rootless-runtime-workflow.test.ts @@ -3,48 +3,53 @@ import { describe, expect, it } from "vitest"; -import { readYaml, type Workflow, type WorkflowStep } from "../../helpers/e2e-workflow-contract"; - -function rootlessLinuxStep(name: string): WorkflowStep { - const workflow = readYaml(".github/workflows/portable-profile-e2e.yaml"); - const step = workflow.jobs["rootless-linux"]?.steps?.find((candidate) => candidate.name === name); - expect(step).toBeDefined(); - return step!; -} +import { readYaml, type Workflow } from "../../helpers/e2e-workflow-contract"; describe("portable profile rootless runtime workflow", () => { - // source-shape-contract: compatibility -- The rootless workflow must compile the exact candidate managed inference catalogue before the live E2E test imports it - it("compiles the managed inference catalogue after dependency installation and before the live E2E test (#9680)", () => { + // source-shape-contract: compatibility -- The workflow must compile the candidate catalogue, pin Ubuntu 26.04 and Podman 5.7, and correct the pasta AppArmor policy before the live E2E test + it("prepares Ubuntu 26.04, Podman 5.7, and the pasta AppArmor policy before the live E2E test (#9006)", () => { const workflow = readYaml(".github/workflows/portable-profile-e2e.yaml"); - const steps = workflow.jobs["rootless-linux"]?.steps ?? []; + const job = workflow.jobs["rootless-linux"]; + const steps = job?.steps ?? []; + const provision = steps.find( + (step) => step.name === "Provision restricted rootless Linux runtime", + )?.run; + const policy = steps.find( + (step) => step.name === "Apply Ubuntu pasta signal policy correction", + )?.run; const dependencyInstallIndex = steps.findIndex( (step) => step.name === "Install root dependencies", ); const catalogueCompileIndex = steps.findIndex((step) => step.run === "npm run catalog:compile"); + const provisionIndex = steps.findIndex( + (step) => step.name === "Provision restricted rootless Linux runtime", + ); + const policyIndex = steps.findIndex( + (step) => step.name === "Apply Ubuntu pasta signal policy correction", + ); const liveTestIndex = steps.findIndex( (step) => step.name === "Exercise portable profile in the rootless environment", ); + const packageInstallIndex = provision?.indexOf("sudo apt-get install") ?? -1; + const packageVersionIndex = provision?.indexOf("dpkg-query --show") ?? -1; + const runtimeVersionIndex = provision?.indexOf("podman --version") ?? -1; + expect(job?.["runs-on"]).toBe("ubuntu-26.04"); + expect(job?.env?.PODMAN_APT_VERSION).toBe("5.7.0+ds2-3build1"); expect(dependencyInstallIndex).toBeGreaterThanOrEqual(0); expect(catalogueCompileIndex).toBeGreaterThan(dependencyInstallIndex); - expect(liveTestIndex).toBeGreaterThan(catalogueCompileIndex); - }); - - it("uses Ubuntu's Podman package with its installed OCI runtime", () => { - const provision = rootlessLinuxStep("Provision restricted rootless Linux runtime").run ?? ""; - const packageInstallIndex = provision.indexOf("sudo apt-get install"); - const packagePodmanIndex = provision.indexOf('package_podman="/usr/bin/podman"'); - const pathExportIndex = provision.indexOf('export PATH="$runtime_bin:$PATH"'); - const jobPathIndex = provision.indexOf('printf \'%s\\n\' "$runtime_bin" >> "$GITHUB_PATH"'); - const versionIndex = provision.indexOf("podman --version"); - + expect(provisionIndex).toBeGreaterThan(catalogueCompileIndex); + expect(policyIndex).toBeGreaterThan(provisionIndex); + expect(liveTestIndex).toBeGreaterThan(policyIndex); expect(packageInstallIndex).toBeGreaterThanOrEqual(0); - expect(provision).toMatch(/sudo apt-get install --yes .*\bpodman\b/); - expect(packagePodmanIndex).toBeGreaterThan(packageInstallIndex); - expect(provision).toContain('ln -s "$package_podman" "$runtime_bin/podman"'); - expect(pathExportIndex).toBeGreaterThan(packagePodmanIndex); - expect(jobPathIndex).toBeGreaterThan(pathExportIndex); - expect(versionIndex).toBeGreaterThan(jobPathIndex); - expect(provision).toContain('test "$(readlink -f "$(command -v podman)")" = "$package_podman"'); + expect(provision).toContain("apparmor"); + expect(provision).toContain('"podman=$PODMAN_APT_VERSION"'); + expect(packageVersionIndex).toBeGreaterThan(packageInstallIndex); + expect(runtimeVersionIndex).toBeGreaterThan(packageVersionIndex); + expect(provision).toContain('test "$package_version" = "$PODMAN_APT_VERSION"'); + expect(provision).toContain('test "$version" = "podman version 5.7.0"'); + expect(policy).toContain("/etc/apparmor.d/usr.bin.pasta"); + expect(policy).toContain("signal (receive) peer=podman,"); + expect(policy).toContain('apparmor_parser -r "$pasta_profile"'); }); }); From 7fdc38be1cc389c72ec992bbd5814705b08a5779 Mon Sep 17 00:00:00 2001 From: Senthil Ravichandran Date: Wed, 19 Aug 2026 20:50:07 -0700 Subject: [PATCH 2/3] fix(ci): recognize Ubuntu 26.04 runner Signed-off-by: Senthil Ravichandran --- .github/actionlint.yaml | 6 ++++++ ci/source-shape-test-budget.json | 2 +- .../portable-profile-rootless-runtime-workflow.test.ts | 8 ++++++-- 3 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 .github/actionlint.yaml diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml new file mode 100644 index 00000000000..bf24c1a77d2 --- /dev/null +++ b/.github/actionlint.yaml @@ -0,0 +1,6 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +self-hosted-runner: + labels: + - ubuntu-26.04 diff --git a/ci/source-shape-test-budget.json b/ci/source-shape-test-budget.json index a489c44cff6..97b0e0bb5f3 100644 --- a/ci/source-shape-test-budget.json +++ b/ci/source-shape-test-budget.json @@ -63,7 +63,7 @@ }, { "file": "test/e2e/support/portable-profile-rootless-runtime-workflow.test.ts", - "test": "prepares Ubuntu 26.04, Podman 5.7, and the pasta AppArmor policy before the live E2E test (#9006)", + "test": "keeps actionlint and live E2E on Ubuntu 26.04 and Podman 5.7 (#9006)", "category": "compatibility" }, { diff --git a/test/e2e/support/portable-profile-rootless-runtime-workflow.test.ts b/test/e2e/support/portable-profile-rootless-runtime-workflow.test.ts index a358d07a3c5..be006d0f807 100644 --- a/test/e2e/support/portable-profile-rootless-runtime-workflow.test.ts +++ b/test/e2e/support/portable-profile-rootless-runtime-workflow.test.ts @@ -6,8 +6,11 @@ import { describe, expect, it } from "vitest"; import { readYaml, type Workflow } from "../../helpers/e2e-workflow-contract"; describe("portable profile rootless runtime workflow", () => { - // source-shape-contract: compatibility -- The workflow must compile the candidate catalogue, pin Ubuntu 26.04 and Podman 5.7, and correct the pasta AppArmor policy before the live E2E test - it("prepares Ubuntu 26.04, Podman 5.7, and the pasta AppArmor policy before the live E2E test (#9006)", () => { + // source-shape-contract: compatibility -- Actionlint and the workflow must share Ubuntu 26.04 while the workflow compiles the candidate catalogue, pins Podman 5.7, and corrects the pasta AppArmor policy before live E2E + it("keeps actionlint and live E2E on Ubuntu 26.04 and Podman 5.7 (#9006)", () => { + const actionlint = readYaml<{ "self-hosted-runner"?: { labels?: string[] } }>( + ".github/actionlint.yaml", + ); const workflow = readYaml(".github/workflows/portable-profile-e2e.yaml"); const job = workflow.jobs["rootless-linux"]; const steps = job?.steps ?? []; @@ -35,6 +38,7 @@ describe("portable profile rootless runtime workflow", () => { const runtimeVersionIndex = provision?.indexOf("podman --version") ?? -1; expect(job?.["runs-on"]).toBe("ubuntu-26.04"); + expect(actionlint["self-hosted-runner"]?.labels).toContain("ubuntu-26.04"); expect(job?.env?.PODMAN_APT_VERSION).toBe("5.7.0+ds2-3build1"); expect(dependencyInstallIndex).toBeGreaterThanOrEqual(0); expect(catalogueCompileIndex).toBeGreaterThan(dependencyInstallIndex); From 5e32afb3306affd96463caf656d1b8a87940336a Mon Sep 17 00:00:00 2001 From: Senthil Ravichandran Date: Wed, 19 Aug 2026 21:32:03 -0700 Subject: [PATCH 3/3] test(e2e): preserve rootless workflow guards Signed-off-by: Senthil Ravichandran --- .../portable-profile-rootless-runtime-workflow.test.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/test/e2e/support/portable-profile-rootless-runtime-workflow.test.ts b/test/e2e/support/portable-profile-rootless-runtime-workflow.test.ts index be006d0f807..f790a3c3058 100644 --- a/test/e2e/support/portable-profile-rootless-runtime-workflow.test.ts +++ b/test/e2e/support/portable-profile-rootless-runtime-workflow.test.ts @@ -36,9 +36,11 @@ describe("portable profile rootless runtime workflow", () => { const packageInstallIndex = provision?.indexOf("sudo apt-get install") ?? -1; const packageVersionIndex = provision?.indexOf("dpkg-query --show") ?? -1; const runtimeVersionIndex = provision?.indexOf("podman --version") ?? -1; + const actionlintLabels = actionlint["self-hosted-runner"]?.labels; expect(job?.["runs-on"]).toBe("ubuntu-26.04"); - expect(actionlint["self-hosted-runner"]?.labels).toContain("ubuntu-26.04"); + expect(Array.isArray(actionlintLabels)).toBe(true); + expect(actionlintLabels).toContain("ubuntu-26.04"); expect(job?.env?.PODMAN_APT_VERSION).toBe("5.7.0+ds2-3build1"); expect(dependencyInstallIndex).toBeGreaterThanOrEqual(0); expect(catalogueCompileIndex).toBeGreaterThan(dependencyInstallIndex); @@ -54,6 +56,12 @@ describe("portable profile rootless runtime workflow", () => { expect(provision).toContain('test "$version" = "podman version 5.7.0"'); expect(policy).toContain("/etc/apparmor.d/usr.bin.pasta"); expect(policy).toContain("signal (receive) peer=podman,"); + expect(policy).toContain('test -f "$pasta_profile"'); + expect(policy).toContain( + `test "$(grep -Fc 'include ' "$pasta_profile")" -eq 1`, + ); + expect(policy).toContain('if ! grep -Eq "$signal_rule" "$pasta_profile"; then'); + expect(policy).toContain('test "$(grep -Ec "$signal_rule" "$pasta_profile")" -eq 1'); expect(policy).toContain('apparmor_parser -r "$pasta_profile"'); }); });