From 236eab43b7a126ad5d99c6bbf7fea70a48e4f4d8 Mon Sep 17 00:00:00 2001 From: Evan Lezar Date: Thu, 1 Oct 2026 10:54:10 +0200 Subject: [PATCH] test(tmachine): add Fedora RPM package installer Signed-off-by: Evan Lezar --- .agents/skills/test-release-canary/SKILL.md | 7 + .github/workflows/branch-e2e.yml | 34 ++++- .../workflows/prepare-integration-inputs.yml | 36 +++++ .github/workflows/release-dev.yml | 10 +- .github/workflows/release-tag.yml | 10 +- CI.md | 5 + tests/ansible/playbooks/openshell-deb.yaml | 92 +------------ tests/ansible/playbooks/openshell-rpm.yaml | 34 +++++ .../tasks/main.yaml | 123 ++++++++++++++++++ .../templates/gateway.toml.j2 | 8 ++ .../tmachine_user_manager/tasks/main.yaml | 8 +- tests/config.nix | 11 ++ 12 files changed, 277 insertions(+), 101 deletions(-) create mode 100644 tests/ansible/playbooks/openshell-rpm.yaml create mode 100644 tests/ansible/roles/openshell_packaged_gateway/tasks/main.yaml create mode 100644 tests/ansible/roles/openshell_packaged_gateway/templates/gateway.toml.j2 diff --git a/.agents/skills/test-release-canary/SKILL.md b/.agents/skills/test-release-canary/SKILL.md index 4b95b805f8..15430d8610 100644 --- a/.agents/skills/test-release-canary/SKILL.md +++ b/.agents/skills/test-release-canary/SKILL.md @@ -32,6 +32,13 @@ before installing a snap. The Debian and Kubernetes CLI lanes remove snapd so they continue to exercise the dev Debian package. Kubernetes pins the matching `0.0.0-dev` chart and `:dev` images. +RPM package installation also has tmachine conformance coverage in +`Branch E2E Checks` (with `test:e2e`), `Release Dev`, and `Release Tag`. Those +lanes use the `rpm` installer on `fedora-podman-rootful` and +`fedora-podman-rootless` with candidate CLI and +gateway RPMs and matching runtime images. Branch RPM package builds run on +every approved branch run, independently of optional E2E labels. + The host-package jobs exercise fresh installs, not upgrades from a persisted schema-v1 gateway config. Validate Homebrew and RPM exact-default migration with the release-tooling and package lifecycle tests before relying on the canary. diff --git a/.github/workflows/branch-e2e.yml b/.github/workflows/branch-e2e.yml index 49cf199a3c..02b3508e76 100644 --- a/.github/workflows/branch-e2e.yml +++ b/.github/workflows/branch-e2e.yml @@ -82,13 +82,15 @@ jobs: version: needs: [pr_metadata] - if: needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_any_e2e == 'true' + if: needs.pr_metadata.outputs.should_run == 'true' permissions: contents: read runs-on: ubuntu-latest timeout-minutes: 5 outputs: cargo: ${{ steps.version.outputs.cargo }} + rpm_version: ${{ steps.version.outputs.rpm_version }} + rpm_release: ${{ steps.version.outputs.rpm_release }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -99,7 +101,13 @@ jobs: id: version run: | cargo="$(python3 tasks/scripts/release.py get-version --cargo)" - echo "cargo=$cargo" >> "$GITHUB_OUTPUT" + rpm_version="$(python3 tasks/scripts/release.py get-version --dev --rpm-version)" + rpm_release="$(python3 tasks/scripts/release.py get-version --dev --rpm-release)" + { + echo "cargo=$cargo" + echo "rpm_version=$rpm_version" + echo "rpm_release=$rpm_release" + } >> "$GITHUB_OUTPUT" build-binaries: needs: version @@ -200,14 +208,30 @@ jobs: packages: write uses: ./.github/workflows/build-images.yml + build-rpm: + name: Build RPM packages + needs: [pr_metadata, version, build-binaries] + if: needs.pr_metadata.outputs.should_run == 'true' + permissions: + actions: read + contents: read + uses: ./.github/workflows/rpm-package.yml + with: + checkout-ref: ${{ github.sha }} + rpm-version: ${{ needs.version.outputs.rpm_version }} + rpm-release: ${{ needs.version.outputs.rpm_release }} + cargo-version: ${{ needs.version.outputs.cargo }} + prepare-integration: - needs: [pr_metadata, build-binaries, build-images] + needs: [pr_metadata, build-binaries, build-images, build-rpm] if: needs.pr_metadata.outputs.run_integration == 'true' permissions: actions: read contents: read packages: read uses: ./.github/workflows/prepare-integration-inputs.yml + with: + rpm-artifact-name: rpm-linux-x86_64 # Run driver-independent conformance tests. conformance-integration: @@ -225,8 +249,8 @@ jobs: [ {"environment":"ubuntu-docker-rootful","installer":"binaries","testsuite":"conformance"}, {"environment":"ubuntu-k3s","installer":"k3s","testsuite":"conformance"}, - {"environment":"fedora-podman-rootful","installer":"binaries","testsuite":"conformance"}, - {"environment":"fedora-podman-rootless","installer":"binaries","testsuite":"conformance"} + {"environment":"fedora-podman-rootful","installer":"rpm","testsuite":"conformance"}, + {"environment":"fedora-podman-rootless","installer":"rpm","testsuite":"conformance"} ] # Run feature-specific integration tests: diff --git a/.github/workflows/prepare-integration-inputs.yml b/.github/workflows/prepare-integration-inputs.yml index d80e4943a1..d9b2c2a9f3 100644 --- a/.github/workflows/prepare-integration-inputs.yml +++ b/.github/workflows/prepare-integration-inputs.yml @@ -21,6 +21,11 @@ on: required: false type: string default: "" + rpm-artifact-name: + description: RPM package artifact to include in the tmachine inputs + required: false + type: string + default: "" outputs: source_sha: description: Source revision of the candidate artifacts @@ -94,6 +99,37 @@ jobs: mv artifacts/packages/download/*.deb artifacts/packages/openshell.deb rmdir artifacts/packages/download + - name: Download RPM package artifact + if: inputs['rpm-artifact-name'] != '' + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: ${{ inputs['rpm-artifact-name'] }} + path: artifacts/packages/rpm/download + github-token: ${{ github.token }} + run-id: ${{ inputs['artifact-run-id'] || github.run_id }} + + - name: Stage RPM package inputs + if: inputs['rpm-artifact-name'] != '' + run: | + set -euo pipefail + shopt -s nullglob + download_dir=artifacts/packages/rpm/download + cli_rpms=("$download_dir"/openshell-[0-9]*.rpm) + gateway_rpms=("$download_dir"/openshell-gateway-[0-9]*.rpm) + if [[ ${#cli_rpms[@]} -ne 1 || ${#gateway_rpms[@]} -ne 1 ]]; then + echo "candidate artifact must contain exactly one CLI and one gateway RPM" >&2 + exit 1 + fi + cli_identity=${cli_rpms[0]%.rpm} + gateway_identity=${gateway_rpms[0]%.rpm} + if [[ ${cli_identity##*.} != "${gateway_identity##*.}" ]]; then + echo "candidate CLI and gateway RPM architectures must match" >&2 + exit 1 + fi + mv "${cli_rpms[0]}" artifacts/packages/rpm/openshell.rpm + mv "${gateway_rpms[0]}" artifacts/packages/rpm/openshell-gateway.rpm + rm -rf "$download_dir" + - name: Log in to GHCR run: echo "${{ github.token }}" | docker login ghcr.io -u "${GITHUB_ACTOR}" --password-stdin diff --git a/.github/workflows/release-dev.yml b/.github/workflows/release-dev.yml index 371144bf7c..80db20c942 100644 --- a/.github/workflows/release-dev.yml +++ b/.github/workflows/release-dev.yml @@ -102,7 +102,7 @@ jobs: checkout-ref: ${{ github.sha }} prepare-integration: - needs: [build-binaries, build-deb, build-images] + needs: [build-binaries, build-deb, build-images, build-rpm] permissions: actions: read contents: read @@ -110,6 +110,7 @@ jobs: uses: ./.github/workflows/prepare-integration-inputs.yml with: deb-artifact-name: deb-linux-amd64 + rpm-artifact-name: rpm-linux-x86_64 conformance-integration: needs: prepare-integration @@ -122,6 +123,13 @@ jobs: category: conformance source-sha: ${{ needs.prepare-integration.outputs.source_sha }} integration-inputs-artifact-id: ${{ needs.prepare-integration.outputs.integration_inputs_artifact_id }} + test-matrix: >- + [ + {"environment":"ubuntu-docker-rootful","installer":"deb","testsuite":"conformance"}, + {"environment":"ubuntu-k3s","installer":"k3s","testsuite":"conformance"}, + {"environment":"fedora-podman-rootful","installer":"rpm","testsuite":"conformance"}, + {"environment":"fedora-podman-rootless","installer":"rpm","testsuite":"conformance"} + ] feature-specific-integration: needs: prepare-integration diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml index 3e18a0936f..3f9a5942ff 100644 --- a/.github/workflows/release-tag.yml +++ b/.github/workflows/release-tag.yml @@ -152,7 +152,7 @@ jobs: CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }} prepare-integration: - needs: [compute-versions, build-binaries, build-deb, build-images] + needs: [compute-versions, build-binaries, build-deb, build-images, build-rpm] permissions: actions: read contents: read @@ -161,6 +161,7 @@ jobs: with: source-sha: ${{ needs.compute-versions.outputs.source_sha }} deb-artifact-name: deb-linux-amd64 + rpm-artifact-name: rpm-linux-x86_64 conformance-integration: needs: prepare-integration @@ -173,6 +174,13 @@ jobs: category: conformance source-sha: ${{ needs.prepare-integration.outputs.source_sha }} integration-inputs-artifact-id: ${{ needs.prepare-integration.outputs.integration_inputs_artifact_id }} + test-matrix: >- + [ + {"environment":"ubuntu-docker-rootful","installer":"deb","testsuite":"conformance"}, + {"environment":"ubuntu-k3s","installer":"k3s","testsuite":"conformance"}, + {"environment":"fedora-podman-rootful","installer":"rpm","testsuite":"conformance"}, + {"environment":"fedora-podman-rootless","installer":"rpm","testsuite":"conformance"} + ] feature-specific-integration: needs: prepare-integration diff --git a/CI.md b/CI.md index 6c07eac087..2efdffd3c2 100644 --- a/CI.md +++ b/CI.md @@ -23,6 +23,11 @@ Windows checks are not required for merging and do not run in merge queues. Main and manual runs also build release binaries, with `continue-on-error: true` so Windows failures do not fail the workflow. +Every approved `Branch E2E Checks` run builds the RPM packages, including +runs without optional E2E labels. Core integration qualification installs the +CLI and gateway RPMs on Fedora with rootful and rootless Podman and runs conformance using +the matching runtime images. Release Dev and Release Tag run the same RPM lane. + Three opt-in labels enable the long-running E2E suites: - `test:e2e` runs the Docker, rootless Podman, Kubernetes, and VM E2E suites diff --git a/tests/ansible/playbooks/openshell-deb.yaml b/tests/ansible/playbooks/openshell-deb.yaml index f1589374d2..2304224cf0 100644 --- a/tests/ansible/playbooks/openshell-deb.yaml +++ b/tests/ansible/playbooks/openshell-deb.yaml @@ -21,94 +21,6 @@ ansible.builtin.apt: deb: /var/tmp/openshell.deb - - name: Copy OpenShell runtime images - become: true - ansible.builtin.copy: - src: "{{ item.src }}" - dest: "/var/tmp/{{ item.name }}.tar" - mode: "0644" - loop: - - name: openshell-sandbox - src: "{{ openshell_sandbox_image }}" - - name: openshell-supervisor - src: "{{ openshell_supervisor_image }}" - - - name: Load OpenShell runtime images - become: true - ansible.builtin.command: - argv: - - docker - - load - - --input - - "/var/tmp/{{ item }}.tar" - loop: - - openshell-sandbox - - openshell-supervisor - - # The package normally starts from its built-in runtime-image defaults. - # Qualification instead pins the candidate images staged by tmachine, so - # keep that override separate from the operator-owned gateway.toml. - - name: Create OpenShell qualification configuration directory - become: true - ansible.builtin.file: - path: /var/lib/openshell-qualification - state: directory - owner: root - group: root - mode: "0755" - - - name: Configure candidate OpenShell runtime images for qualification - become: true - ansible.builtin.copy: - dest: /var/lib/openshell-qualification/gateway.toml - owner: root - group: root - mode: "0644" - content: | - [openshell] - version = 2 - - [openshell.drivers.docker] - sandbox_runtime_image = "docker.io/openshell/sandbox:tmachine" - supervisor_image = "docker.io/openshell/supervisor:tmachine" - - - name: Create OpenShell environment directory - ansible.builtin.file: - path: /home/tmachine/.config/openshell - state: directory - mode: "0700" - - - name: Select qualification gateway configuration - ansible.builtin.copy: - dest: /home/tmachine/.config/openshell/gateway.env - mode: "0600" - content: | - OPENSHELL_GATEWAY_CONFIG=/var/lib/openshell-qualification/gateway.toml - - - name: Start tmachine user manager - ansible.builtin.include_role: - name: tmachine_user_manager - - - name: Start packaged OpenShell gateway service - ansible.builtin.systemd_service: - name: openshell-gateway.service - scope: user - daemon_reload: true - enabled: true - state: started - environment: - XDG_RUNTIME_DIR: /run/user/1000 - DBUS_SESSION_BUS_ADDRESS: unix:path=/run/user/1000/bus - - - name: Wait for OpenShell gateway - ansible.builtin.wait_for: - host: 127.0.0.1 - port: 17670 - timeout: 60 - - - name: Register packaged OpenShell gateway + - name: Prepare packaged OpenShell gateway ansible.builtin.include_role: - name: openshell_client - vars: - openshell_client_gateway_endpoint: https://127.0.0.1:17670 - openshell_client_gateway_name: openshell + name: openshell_packaged_gateway diff --git a/tests/ansible/playbooks/openshell-rpm.yaml b/tests/ansible/playbooks/openshell-rpm.yaml new file mode 100644 index 0000000000..e3c7934790 --- /dev/null +++ b/tests/ansible/playbooks/openshell-rpm.yaml @@ -0,0 +1,34 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +--- +- name: Install OpenShell RPM packages + hosts: all + gather_facts: false + tasks: + - name: Wait for SSH + ansible.builtin.wait_for_connection: + + - name: Copy OpenShell RPM packages + become: true + ansible.builtin.copy: + src: "{{ item.src }}" + dest: "/var/tmp/{{ item.name }}.rpm" + mode: "0644" + loop: + - { name: openshell, src: "{{ openshell_rpm }}" } + - { name: openshell-gateway, src: "{{ openshell_gateway_rpm }}" } + + - name: Install OpenShell RPM packages + become: true + ansible.builtin.dnf: + name: + - /var/tmp/openshell.rpm + - /var/tmp/openshell-gateway.rpm + state: present + disable_gpg_check: true + allow_downgrade: true + + - name: Prepare packaged OpenShell gateway + ansible.builtin.include_role: + name: openshell_packaged_gateway diff --git a/tests/ansible/roles/openshell_packaged_gateway/tasks/main.yaml b/tests/ansible/roles/openshell_packaged_gateway/tasks/main.yaml new file mode 100644 index 0000000000..a3e04f52bf --- /dev/null +++ b/tests/ansible/roles/openshell_packaged_gateway/tasks/main.yaml @@ -0,0 +1,123 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +--- +- name: Detect package gateway runtime + ansible.builtin.include_role: + name: openshell_gateway + tasks_from: detect.yaml + +- name: Create runtime image staging directory + become: true + ansible.builtin.file: + path: /var/lib/openshell + state: directory + owner: "{{ openshell_gateway_user }}" + group: "{{ openshell_gateway_user }}" + mode: "0700" + +- name: Load candidate runtime images + ansible.builtin.include_role: + name: openshell_gateway + tasks_from: "{{ openshell_gateway_driver }}.yaml" + +# Package defaults select published images. Keep candidate image overrides +# separate from the operator-owned gateway.toml in both installers. +- name: Create qualification configuration directory + become: true + ansible.builtin.file: + path: /var/lib/openshell-qualification + state: directory + owner: root + group: root + mode: "0755" + +- name: Configure candidate runtime images for qualification + become: true + ansible.builtin.template: + src: gateway.toml.j2 + dest: /var/lib/openshell-qualification/gateway.toml + owner: root + group: root + mode: "0644" + +- name: Create packaged gateway environment directory + become: true + ansible.builtin.file: + path: "{{ openshell_gateway_home }}/.config/openshell" + state: directory + owner: "{{ openshell_gateway_user }}" + group: "{{ openshell_gateway_user }}" + mode: "0700" + +- name: Select qualification gateway configuration + become: true + ansible.builtin.copy: + dest: "{{ openshell_gateway_home }}/.config/openshell/gateway.env" + owner: "{{ openshell_gateway_user }}" + group: "{{ openshell_gateway_user }}" + mode: "0600" + content: | + OPENSHELL_GATEWAY_CONFIG=/var/lib/openshell-qualification/gateway.toml + +- name: Start gateway user manager + ansible.builtin.include_role: + name: tmachine_user_manager + vars: + tmachine_user_manager_user: "{{ openshell_gateway_user }}" + tmachine_user_manager_uid: "{{ openshell_gateway_uid }}" + +- name: Start packaged gateway service + become: true + become_user: "{{ openshell_gateway_user }}" + ansible.builtin.systemd_service: + name: openshell-gateway.service + scope: user + daemon_reload: true + enabled: true + state: started + environment: + HOME: "{{ openshell_gateway_home }}" + XDG_RUNTIME_DIR: "/run/user/{{ openshell_gateway_uid }}" + DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ openshell_gateway_uid }}/bus" + +- name: Wait for packaged gateway + ansible.builtin.wait_for: + host: 127.0.0.1 + port: 17670 + timeout: 60 + +# The rootful Podman gateway uses root's image store and packaged user service. +# Copy only client credentials so the test runner can register that gateway +# without granting access to root's home or copying server signing keys. +- name: Prepare test client for rootful gateway + when: openshell_gateway_user == 'root' + block: + - name: Read rootful gateway client credentials + become: true + ansible.builtin.slurp: + src: "/root/.local/state/openshell/tls/{{ item }}" + loop: [ca.crt, client/tls.crt, client/tls.key] + register: openshell_packaged_gateway_client_credentials + no_log: true + + - name: Create test client credential directory + ansible.builtin.file: + path: /home/tmachine/.config/openshell/gateways/openshell/mtls + state: directory + mode: "0700" + + - name: Install test client credentials + ansible.builtin.copy: + content: "{{ item.content | b64decode }}" + dest: "/home/tmachine/.config/openshell/gateways/openshell/mtls/{{ item.item | basename }}" + mode: "0600" + loop: "{{ openshell_packaged_gateway_client_credentials.results }}" + no_log: true + +- name: Register packaged gateway + ansible.builtin.include_role: + name: openshell_client + vars: + openshell_client_gateway_endpoint: https://127.0.0.1:17670 + openshell_client_gateway_name: openshell diff --git a/tests/ansible/roles/openshell_packaged_gateway/templates/gateway.toml.j2 b/tests/ansible/roles/openshell_packaged_gateway/templates/gateway.toml.j2 new file mode 100644 index 0000000000..cb6d524228 --- /dev/null +++ b/tests/ansible/roles/openshell_packaged_gateway/templates/gateway.toml.j2 @@ -0,0 +1,8 @@ +{# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. #} +{# SPDX-License-Identifier: Apache-2.0 #} +[openshell] +version = 2 + +[openshell.drivers.{{ openshell_gateway_driver }}] +sandbox_runtime_image = "docker.io/openshell/sandbox:tmachine" +supervisor_image = "docker.io/openshell/supervisor:tmachine" diff --git a/tests/ansible/roles/tmachine_user_manager/tasks/main.yaml b/tests/ansible/roles/tmachine_user_manager/tasks/main.yaml index 1b2f2232af..5758331123 100644 --- a/tests/ansible/roles/tmachine_user_manager/tasks/main.yaml +++ b/tests/ansible/roles/tmachine_user_manager/tasks/main.yaml @@ -2,17 +2,17 @@ # SPDX-License-Identifier: Apache-2.0 --- -- name: Enable lingering for tmachine +- name: Enable lingering for gateway user become: true ansible.builtin.command: argv: - loginctl - enable-linger - - tmachine + - "{{ tmachine_user_manager_user | default('tmachine') }}" changed_when: false -- name: Start tmachine user manager +- name: Start gateway user manager become: true ansible.builtin.systemd_service: - name: user@1000.service + name: "user@{{ tmachine_user_manager_uid | default('1000') }}.service" state: started diff --git a/tests/config.nix b/tests/config.nix index 4bf7488ff8..26d27b712b 100644 --- a/tests/config.nix +++ b/tests/config.nix @@ -126,6 +126,17 @@ let openshell_sandbox_image = "../artifacts/images/openshell-sandbox-tmachine.tar"; }; } + { + name = "rpm"; + use_galaxy = false; + playbooks = [ "ansible/playbooks/openshell-rpm.yaml" ]; + inputs = { + openshell_rpm = "../artifacts/packages/rpm/openshell.rpm"; + openshell_gateway_rpm = "../artifacts/packages/rpm/openshell-gateway.rpm"; + openshell_supervisor_image = "../artifacts/images/openshell-supervisor-tmachine.tar"; + openshell_sandbox_image = "../artifacts/images/openshell-sandbox-tmachine.tar"; + }; + } ]; testsuites = [