Skip to content

feat(ci): wire the amd-repo-setup package into native package CI - #7280

Merged
lsudarsh-amd merged 51 commits into
mainfrom
shared/amdrocm-repo-ci
Oct 22, 2026
Merged

lsudarsh-amd merged 51 commits into
mainfrom
shared/amdrocm-repo-ci

Conversation

@lsudarsh-amd

@lsudarsh-amd lsudarsh-amd commented Aug 11, 2026 •

Copy link
Copy Markdown
Contributor

Motivation

Setting up a ROCm package repository currently takes a user several manual steps. The amd-repo-setup package reduces that to a single install: it ships the repository configuration file and, on the signed streams, the signing key.

This pull request wires the package into the native package pipeline. On the nightly and prerelease lines it builds the package for each distro, checks its contents, and uploads it to the run's artifacts bucket. It is off by default (enable_repo_setup_package: false), so nothing runs until a caller turns it on.

This is the fourth of five pull requests:

It is stacked on #7276, which is stacked on #7269, so it merges after both.

Contributes to #6986.

Technical Details

Everything is in .github/workflows/multi_arch_build_native_linux_packages.yml, apart from one new script and its unit tests.

Jobs

Job Purpose Runner
resolve_repo_setup_params One get-repo-setup-params call: the profile matrix, each profile's container image, and the stream, tier, repository URL, signing-key URL and nightly sub-folder The build job's runner and container
stage_repo_setup_package Per profile, in that distro's container: build the package, inspect it, then upload it to <run_id>-linux/amd-repo-setup/<os_profile>/amd-repo-setup-<tier>.<format> ubuntu-26.04
notify_quartz_completed Reports the jobs' result to Quartz; resolve_repo_setup_params reports the start ubuntu-26.04, continue-on-error

When it runs. It runs only when enable_repo_setup_package is true, on nightly and prerelease. No caller sets it yet.

  • prerelease configures the rc stream.
  • release is left out: it configures a stream, but has no artifacts bucket to upload into.
  • ASAN builds skip, because their repositories are promoted under packages-asan/, which the package's repository URL does not point at.
  • TSAN builds skip, because they are not promoted at all.

Failure handling. Neither job uses continue-on-error, so once the switch is on, a failure fails the run.

Credentials. AWS credentials are configured only after the package is built and inspected, so the key fetch, the repository check and the build run without them.

Upload URL. The upload step (id: upload-repo-setup-package) exposes the package's URL, which #7276's script writes to GITHUB_OUTPUT as repo_setup_package_url, to later steps in the job.

Inspect step

"Inspect amd-repo-setup" runs build_tools/packaging/linux/inspect_repo_setup_package.py on the package the job just built, before the upload. It lists the archive (dpkg-deb or rpm, which the build already needs) and checks:

  • the repository file for this stream, and on a signed stream the key, are at the expected paths;
  • nothing it must not ship is present, including the key paths that were renamed away from generic rocm names;
  • everything is owned by root.

The package type comes from the OS profile. Checking with the job's own stream also confirms that the package configures that stream. A failure stops the job before the upload.

The package is built once, in this job, with the real parameters, in the target distro's image.

Reads of a published repository

On prerelease. stage_repo_setup_package fetches the rc signing key; the builder accepts only the pinned key. It also uses --verify-repo-url to check that the repository index is reachable. Either failing fails the run, which catches a package that installs but cannot refresh.

On nightly. Neither check runs. Nightly is unsigned, and the builder skips the URL check because the run's dated folder is published by the same run.

No pull request reaches a published repository: the jobs don't run on the ci line.

Note for reviewers

The nightly package points at the run's dated folder, YYYYMMDD-<run_id>. Two jobs stamp that date: resolve_repo_setup_params when it runs, and release promotion when it runs.

So two kinds of run produce a nightly package that points at a folder that doesn't exist:

  • a run that crosses UTC midnight between the two;
  • a promotion re-run on a later day.

Recent nightlies build their content between 01:37 and 02:10 UTC, well clear of midnight, so they are not affected. A nightly dispatched by hand late in the UTC day, or re-run on a later day, is.

The fix touches promotion, which every package type shares, so it will follow separately. It's one of the things to settle before the switch is turned on for nightly.

Test Plan

Verification used the state after the stack merges, entirely in throwaway containers, with MinIO standing in for S3.

What it proves How
The new script is correct Unit tests over path derivation, both listing parsers, the payload check, finding the built package and the command line, with fixtures in the format of dpkg-deb -c and rpm -qp output
Nothing else regressed The full suite, compared by test ID against #7276
The setup job works Its run: steps, taken from the workflow and replayed in its pinned container, for prerelease, nightly and release × deb and rpm
The stage job works Its run: steps, taken from the workflow and replayed in each distro's container for prerelease and nightly: build → inspect → upload
Inspect stops a bad package The built package is tampered with right after the build, then the job continues
The conditions are right The if: expressions evaluated over every line and build variant, with the switch on and off, and the Quartz job over every setup and stage result
The packages install The rc and nightly packages installed per the user docs on ubuntu2404, rhel8, rhel10 and sles16, then a full amdrocm dry run
CI hygiene actionlint, pre-commit, zizmor and the unit tests that walk the workflow directory

Test Result

What it proves Result
The new script is correct 74 unit tests passed. Unit Tests passes on ubuntu-26.04 and windows-2022.
Nothing else regressed 0 status changes against #7276, 74 tests added, none removed
The setup job works 6 of 6, each with all 7 outputs
The stage job works 8 of 8. Each upload is byte-identical to the built package, at the expected key, with nothing else in its folder. rc builds against the live repository and key
Inspect stops a bad package A clean repack passes and uploads. A superseded key path, a non-root owner, a package for the wrong stream, two packages and no package each fail at Inspect, and nothing is uploaded (9 of 9)
The conditions are right Switch off: the jobs run on 0 of 63 line × variant combinations. Switch on: 4 (nightly and prerelease, without sanitizers). The Quartz completion runs only when Quartz is enabled and the setup job ran
The packages install 8 of 8, plus a full amdrocm dry run (255–333 packages resolve)
CI hygiene All pass. zizmor in the security scan reports no findings; the stage job's container image is picked per distro at runtime, so it carries an inline unpinned-images exemption.

Not verifiable outside GitHub Actions; worth watching on the first run with the switch on:

  1. The stage job's per-distro containers, and the AWS credentials action inside them, on GitHub's ubuntu-26.04 runners.
  2. The real upload.
  3. The Quartz reports.
  4. The setup container on the scale runner.

Submission Checklist

@therock-pr-bot

therock-pr-bot Bot commented Aug 11, 2026 •

Copy link
Copy Markdown

✅ All Checks Passed — Ready for Review

Check Status Details
📝 PR Description ✅ Pass —
⛔ Forbidden Files ✅ Pass —
🧪 Unit Test ✅ Pass —
🔎 pre-commit ✅ Pass —
🚫 Draft PR 🔜 To Be Enabled —
🚩 Feature Flag 🔜 To Be Enabled —
📊 Code Coverage 🔜 To Be Enabled —
🤖 therock-pr-bot ✅ Pass —

🎉 All checks passed! This PR is ready for review.

📖 Need help? See the Policy FAQ for details on every check and how to fix failures.

🙋 Wish to Override Policy?

@therock-pr-bot

Copy link
Copy Markdown

🎉 All checks passed! This PR is ready for review.

Derives the publish destination from WorkflowOutputRoot and writes
through StorageBackend rather than taking a bucket and prefix and
driving boto3 directly. The new output type sits beside the content
repository rather than inside it: one package is built per OS profile
and several share a filename, so indexing them together would collide.

Also adds the URL helpers the workflow invokes and an install-harness
mode that configures the repository by installing the built package.
Adds a configuration package pointing apt, dnf, or zypper at a public
ROCm repository, so ROCm installs and updates through the native package
manager instead of a manual multi-step setup.

One package is generated per OS profile and release line. The version
carries the line, so packages from different lines are never confusable,
and URL derivation is isolated in one function because the published
layout is expected to change. For signed lines the signing key is
fetched and fingerprint-checked at build time; it is never stored in the
source tree.

It conflicts with amdgpu-install, whose competing ROCm repository uses
different filenames and sits above the default priority, so nothing
else would stop it silently winning. On apt that conflict is settled by
removal, which leaves the configuration behind — hence the deb postinst
pointing at "apt purge".

build_tools/.gitignore is removed: its unanchored "repo" pattern would
hide the new template/repo/ tree from git, and the tool it guarded is
long gone.
ROCm 10 ships from <stream>.repo.amd.com. The legacy packages-multi-arch
release line is frozen at ROCm 7.14, so a package built against it
points users three releases back.

Streams are modelled by layout shape, not name: flat (stable) serves one
repository per distro, per-build (nightly) adds a YYYYMMDD-runid
segment. Signing follows from the shape rather than a parallel list, so
the two cannot drift apart.

  --release-type prerelease  ->  --stream stable
  amdrocm.sources            ->  amdrocm-<stream>.sources

The stream in the filename stops one stream's package silently replacing
another's config. The key is passed whole via --gpg-key-url; it sits
outside the packages base and cannot be derived from it. The deb822 file
gains X-Repo-Id and Enabled, matching the published install
instructions.

rc is absent: it resolves but serves no content, so a package pointing
there would install and then find nothing.

Verified in containers across ubuntu2404/rhel8/rhel10/sles16 on both
streams: build, install, clean refresh, signature check, amdrocm
10.0.0-4.
The workflow needs three values to build amdrocm-repo: which stream a
build line configures, that stream's packages base, and its signing key
URL. Add a single table carrying all three, replacing the base-URL-only
map that pointed at the retired packages-multi-arch hosts.

The mapping is deliberately not s3_buckets.get_release_stream(). That
answers which product bucket a build publishes into, and its answers do
not transfer here: it maps prerelease to rc, which serves no content on
any distro, and it raises on release, because stable is promoted
manually and has no artifacts bucket -- yet stable is the stream users
install from. release_type stays the workflow input because it also
selects bucket credentials; the stream is derived from it.

The key URL is emitted whole rather than as a root for the builder to
append to, so where the key lives stays a fact recorded here.

Also fix three promotion tests that pinned v4/packages/ as the
destination. That prefix moved to v5/rocm/core/packages/, which turned
them red without the code under test changing. They now discover the
destination from what the promotion actually wrote and assert only the
tail, since what they exist to prove is that the copy is recursive
enough to carry repo/<profile>/ -- not where the release lands.
@lsudarsh-amd
lsudarsh-amd force-pushed the shared/amdrocm-repo-ci branch from 45b1e5a to f7750a8 Compare August 28, 2026 16:29
@dbieleck

Copy link
Copy Markdown

Please review.

@ScottTodd ScottTodd left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please review.

Please mark as ready for review if you want a code review: https://github.com/ROCm/TheRock/blob/main/CONTRIBUTING.md#requesting-a-code-review

This is the fourth of four pull requests:
It stays a draft until #7269 and #7276 merge. Until then unit-tests, Build DEB Packages and Build RPM Packages fail,

You can use stacked pull requests for this: https://docs.github.com/en/pull-requests/how-tos/stacked-pull-requests

Comment on lines +115 to +127
- name: Derive amdrocm-repo parameters
id: derive
run: |
profiles="$(python ./build_tools/packaging/linux/build_repo_package.py list-profiles \
--pkg-type '${{ inputs.native_package_type }}')"
echo "os_profiles=${profiles}" >> "$GITHUB_OUTPUT"
python ./build_tools/packaging/linux/get_url_repo_params.py get-container-image-map \
--os-profiles "${profiles}"
python ./build_tools/packaging/linux/get_url_repo_params.py get-public-repo-base-url \
--release-type '${{ inputs.release_type }}'
python ./build_tools/packaging/linux/get_url_repo_params.py get-nightly-sub-folder \
--release-type '${{ inputs.release_type }}' \
--run-id '${{ inputs.artifact_run_id != '' && inputs.artifact_run_id || github.run_id }}'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why so many calls to get_url_repo_params.py? It looks like a single script call could handle all of this, including writing to GITHUB_OUTPUT (see gha_set_output in https://github.com/ROCm/TheRock/blob/main/build_tools/github_actions/github_actions_api.py)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's a fair point. I've updated the code to use one get-repo-params call to set all six outputs with gha_set_output. The subcommand itself is in #7276, since that PR owns get_url_repo_params.py.

Comment on lines +220 to +241
# Build the amdrocm-repo package for every OS profile of this package type
# and check that its payload is the expected files, at the expected paths,
# owned by root. This container can build both package types but has
# neither dnf nor zypper, so the packages are inspected rather than
# installed; per-distro install coverage lives in
# test_native_linux_packages_install.yml.
#
# Restricted to the pull-request line. It is a gate there, and a gate has
# to be able to fail the job -- which on a release line would also stop the
# content packages being uploaded. The released lines build the real
# package in stage_repo_package instead, which cannot block them.
#
# The repository URL is a placeholder and is never contacted: the build
# needs no key it has to fetch and no repository it has to reach.
- name: Build and inspect amdrocm-repo packages
if: inputs.enable_repo_package && inputs.release_type == 'ci'
run: |
python ./build_tools/packaging/linux/inspect_repo_package.py \
--pkg-type "${{ inputs.native_package_type }}" \
--rocm-version "${{ inputs.rocm_version }}" \
--repo-base-url https://example.com/rocm/core/packages \
--repo-sub-folder 20000101-inspect

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CI and release builds should run the same code whenever possible. See the pinned issue in the repository: #3177.

(also please be careful with AI authored code commenting on "gates" - the terminology used by github is "required check" and multi-arch CI is not currently a required check in this repository, https://github.com/ROCm/TheRock/blob/main/TESTING.md#therock-feature-area-packaging is what this code should follow)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, thanks. I'd previously limited this step to ci because I thought it was a required check that shouldn't block releases. It now runs on every line and I've removed the "gate" wording.

Comment on lines +274 to +282
# Build the amdrocm-repo package for each OS profile of this package type on the
# public release lines and stage it as a workflow artifact. Best-effort: a build
# failure here must never block the release, so the job cannot fail the workflow.
# Publishing to a public download location is handled separately.
stage_repo_package:
name: Stage amdrocm-repo (${{ matrix.os_profile }})
needs: [setup_repo_params]
if: inputs.enable_repo_package && needs.setup_repo_params.outputs.repo_base_url != ''
continue-on-error: true

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Build failures should absolutely block releases. What are you trying to do here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I first implemented this, I thought we wouldn't want a repo package build failure to hold up a release, since it technically sits outside the main ROCm stack. That said, I do agree with you. It ships as part of a finished product, so a failure here should block the release. Code has been updated accordingly.

Comment on lines +344 to +349
- name: Upload amdrocm-repo package
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: amdrocm-repo-${{ inputs.native_package_type }}-${{ matrix.os_profile }}
path: repo-package-out/*.${{ inputs.native_package_type }}
if-no-files-found: error

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fixed now. The artifact upload, download and the separate publish job are gone. The package is now built and published to S3 in the same job.

Comment on lines +421 to +444
- name: Publish amdrocm-repo package
id: publish
run: |
# Bucket and per-run prefix are derived by WorkflowOutputRoot, the
# same way the content upload resolves them, so the file lands beside
# the content packages. ARTIFACT_RUN_ID matches what that upload used.
# Resolution reads RELEASE_TYPE, GITHUB_REPOSITORY and the event
# payload, all present in this job.
#
# One artifact holds one package. Match explicitly rather than globbing
# into a variable: several matches would be joined by newlines and
# passed on as a single filename.
mapfile -t pkgs < <(find repo-package-out -maxdepth 1 -type f \
-name "*.${{ inputs.native_package_type }}")
if [ "${#pkgs[@]}" -ne 1 ]; then
echo "::error::expected exactly one .${{ inputs.native_package_type }} in repo-package-out, found ${#pkgs[@]}"
exit 1
fi
pkg="${pkgs[0]}"
python build_tools/packaging/linux/publish_repo_package.py \
--file "$pkg" \
--run-id "${ARTIFACT_RUN_ID}" \
--os-profile "${{ matrix.os_profile }}" \
--pkg-type "${{ inputs.native_package_type }}"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's some scary inline bash here and comments that are better suited inside script files. Please follow the style guide: https://github.com/ROCm/TheRock/blob/main/docs/development/style_guides/github_actions_style_guide.md#prefer-python-scripts-over-inline-bash

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cleaned this up. The shell that found the built package is now inspect_repo_package.py locate, and I've trimmed the workflow comments, with the detail now in the scripts' docstrings.

lsudarsh-amd and others added 7 commits September 15, 2026 12:07
amdrocm-repo produced one filename for every debian-family profile and
carried no build number:

  amdrocm-repo_10.0.0_all.deb -> amdrocm-repo_10.0.0-1~ubuntu2404_all.deb

The token is the os-profile name, not a Debian codename, which would not
sort: bullseye ranks above bookworm. rpm takes it from an explicit
dist_tag rather than %{?dist}, which reads the build host and is empty on
SUSE; RHEL names are unchanged.

rc is now built, reversing an earlier commit that skipped it for serving
no content: RFC0012 requires stable and rc at v1. Also adds a
message-only %post mirroring the deb postinst.
Add three jobs to multi_arch_build_native_linux_packages.yml:
setup_repo_params resolves the profile matrix, images and repository
URLs; stage_repo_package builds the package per profile on the released
lines; publish_repo_package uploads it beside the native content
packages. The latter two are continue-on-error, so a repo-package
failure cannot block a release.

Add a build-and-inspect step to build_native_packages, on the ci release
line only. It builds every profile unsigned and signed and checks the
payload: expected files at expected paths, owned by root, with the
signing key only at its current path. The build is offline against a
placeholder URL and never contacts a published repository; the container
has no dnf or zypper, so packages are inspected rather than installed.

Gate all of it behind enable_repo_package, which defaults to true so
existing callers need no change.
Pass the resolved stream, packages base and signing-key URL through to
the builder in place of the release line, matching the retargeted CLI.

Drop prerelease from the allowlist. Its stream is rc.repo.amd.com, which
resolves but serves no content on any distro, so the job would produce a
package that cannot refresh. The job now skips rather than staging a
broken artifact; restoring it is one entry in the stream table plus this
list.

The inspector follows the same rename, and takes the stream when
deriving the repo file path now that the filename carries it.

Add timeout-minutes to the three checkout steps this workflow
introduces, as workflow_step_timeouts_test.py requires of new steps. The
pre-existing checkout is left alone as tracked debt.
The install harness looked for a repo called "amdrocm", but the package
registers amdrocm-<stream>, so rpm refreshes and deb checks failed on
every stream. It now reads the repo id, file names and key paths from
build_repo_package instead of hardcoding them.

Also:
- map the prerelease line to the rc stream
- replace the three repo-param subcommands with one get-repo-params
- add jinja2 to the harness test requirements
- tidy comments and docstrings
The key check only looked at the first key in the downloaded file, so a
file with an extra key appended still passed and both keys ended up
trusted. Now the file must contain just the pinned key, and only that
key is embedded. The tests use real gpg instead of mocks.

The docs said installing another stream's deb over the current one
switches repos. It doesn't: the old .sources stays behind. They now say
to purge first on Ubuntu, and to use --oldpackage on SLES.

Also swaps a link to an internal repo for the public install docs,
fixes the --repo-base-url help, and corrects a few wrong comments.
Build and publish the package in one job straight to S3, without
upload-artifact, and let failures fail the run. Parameters come from a
single get-repo-params call, and the inspect step runs on every line
for every stream.

Also:
- enable the prerelease line (rc stream); skip ASAN builds for now
- add inspect_repo_package.py locate in place of inline shell
- configure artifacts credentials only after the build; give the
  setup job read-only permissions
- move expressions out of run: bodies; stop persisting credentials
- Restore workflow_outputs.py, publish_rocm_to_release_buckets.py, their
  tests and their docs to the base branch. The repo package's
  WorkflowOutputRoot location and its publishing to v5/amd-repo-setup/
  move to the next PR, so the shared files get their own review.
- Build the upload key in upload_repo_setup_package.py from the run's
  bucket and prefix until that PR adds the WorkflowOutputRoot method.
  The key is unchanged.
- Publishing the repo package to v5/amd-repo-setup/ moves to a new
  PR, so the comment no longer says promotion publishes it.
- Rename the package to amd-repo-setup, the same name as the
  amd-repo-setup/ folder it will be published to.
- Rename the files that carry the name: the rpm spec template, the
  builder (build_repo_setup_package.py) and its test. The builder and
  its tests pick the spec template by package name.
- Use repo-setup-package-out as the output directory in the docs.
- The repo files, repo ids and signing key keep their amdrocm names,
  which come from the documented manual setup.
- Follow the builder's rename: build_repo_setup_package, the
  get-repo-setup-params subcommand, --repo-setup-package-dir and
  REPO_SETUP_PACKAGE_DIR, and amd-repo-setup in names and messages.
- The harness finds the built or published package by the builder's
  PACKAGE_NAME, and its tests take the file names and the spec template
  from it too. New tests check that a package with another name is not
  picked up and that the published file name is found.
- Upload the package as amd-repo-setup-<tier>.<fmt>.
- Rename the input to enable_repo_setup_package and the jobs to
  resolve_repo_setup_params and stage_repo_setup_package, and use
  amd-repo-setup in the job and step names.
- Rename inspect_repo_package.py to inspect_repo_setup_package.py, with
  its test, and call the renamed builder and get-repo-setup-params.
- Build into repo-setup-package-out.
@lsudarsh-amd lsudarsh-amd changed the title feat(ci): wire the amdrocm-repo package into native package CI feat(ci): wire the amd-repo-setup package into native package CI Oct 19, 2026
The signing-key fetch now retries only timeouts and rate limiting, like
verify_repo_url. Any other HTTP error, such as a 404 from a wrong
--gpg-key-url, fails on the first attempt.

When gpg fails, the error now includes gpg's own output instead of just
the exit status.
# skipped job's outputs are empty strings, which fromJSON cannot parse.
# Fall back to an empty matrix so this job skips cleanly.
os_profile: ${{ fromJSON(needs.resolve_repo_setup_params.outputs.os_profiles || '[]') }}
runs-on: ubuntu-24.04

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are recent changes to make every thing 26.04. Please check that and update

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the heads up. Code has been updated. The three new jobs now run on ubuntu-26.04, like main.

# amd-repo-setup/<os_profile>/.
stage_repo_setup_package:
name: Stage amd-repo-setup (${{ matrix.os_profile }})
needs: [resolve_repo_setup_params]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need native packages build step also in dependency?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't be needed. The package only uses the setup job's parameters, nothing from the native build, and in the release workflow publish_to_release_buckets already waits for both native package builds, which include this job.

@nunnikri nunnikri left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall LGTM

@arvindcheru arvindcheru left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM with open items — align ubuntu-26.04 runner fallbacks with main, and clarify needs vs native package builds.

After a real upload to S3, the upload script now writes the package's
https URL to GITHUB_OUTPUT as repo_setup_package_url, so later steps can
use it. A dry run or a local staging run uploads nothing and writes no
output.
The setup, stage and Quartz completion jobs now use ubuntu-26.04, like
the rest of the workflows on main. The upload step also gets an id, so
later steps can read the package URL it writes to GITHUB_OUTPUT.
@lsudarsh-amd

Copy link
Copy Markdown
Contributor Author

LGTM with open items — align ubuntu-26.04 runner fallbacks with main, and clarify needs vs native package builds.

Thanks @arvindcheru. New jobs now run on ubuntu-26.04. Dependency on native package builds should not be needed here.

lsudarsh-amd added a commit that referenced this pull request Oct 22, 2026
…#7269)

## Motivation

Installing ROCm today needs a manual repository setup. The user has to
fetch and dearmor the AMD signing key, write a sources or repo file by
hand, and look up the correct URL for their distribution and stream.

This change adds `amd-repo-setup`, a repo configuration package that
replaces that multi-step manual setup. Once it is installed, ROCm can be
installed and updated with the native package manager.

This is the second of five pull requests:

- PR 0 (#7004) added `packaging/linux/tests` to `testpaths`, so the
tests the later ones add actually run in CI.
- PR 1 (this one) adds the builder, its templates, its unit tests and
the user documentation.
- PR 2 (#7276) adds the build parameters (`get-repo-setup-params`), the
upload to the run's artifacts bucket and the install-harness package
mode.
- PR 3 (#7280) wires the CI workflow: build, inspect and upload, off by
default.
- PR 4 (#9352) adds the `WorkflowOutputRoot` location for the package
and publishes it to `v5/amd-repo-setup/` during release promotion.

Contributes to #6986.

## Technical Details

`build_repo_setup_package.py` renders the package from the Jinja
templates under `template/repo/`. It builds one package per OS profile
(`ubuntu2404`, `rhel8`, `rhel10`, `sles16`) and stream (`stable`, `rc`,
`nightly`).

**Repository URLs are derived per OS profile.** The URL is
`--repo-base-url` plus the profile, for example
`https://stable.repo.amd.com/rocm/core/packages/ubuntu2404/`; rpm adds
`x86_64/`. Nightly also adds the folder for one build
(`--repo-sub-folder`).

**The package is `amd-repo-setup`**, the same name as the
`amd-repo-setup/` folder it will be published to (PR 4). The files that
carry the name follow it: the builder, its test and the rpm spec
template (`amd-repo-setup.spec.j2`, picked by package name). The
repository the package configures keeps its `amdrocm` names, the same
ones the documented manual setup uses: repo id `amdrocm-stable`, key
`amdrocm.gpg`.

**Package names include the distro and a build number**:
`amd-repo-setup_10.0.0-1~ubuntu2404_all.deb`,
`amd-repo-setup-10.0.0-1.stable.el8.noarch.rpm`. The build number is
`--version-suffix` (default `1`). The docs have a table of examples.

**The package version is `--rocm-version`, as given, on every stream.**
CI passes the version TheRock computes for the release type, the same
one the ROCm packages in that repository carry (`10.1.0`, `10.1.0~pre3`,
`10.2.0~20261013`). So the package sorts the way they do, below stable
at the same ROCm version, and no two streams share a package name. rpm
also carries the stream in the release field (`1.stable.el8`).

**The signing key is fetched at build time.** For signed streams it is
retrieved over https and size-bounded. A missing key URL fails at once;
only timeouts and rate limiting are retried. The file must contain only
the pinned AMD key, and only that key is embedded. It is never stored in
the source tree. For offline builds, `--gpg-key-file` embeds a local key
as provided. The nightly repository is unsigned upstream, so nightly
packages ship no key and disable signature checking.

**The package conflicts with `amdgpu-install`**, which configures a
different ROCm repository and raises it above the default priority. Its
repository files use different names from ours, so nothing else stops
both being installed, and its repository then wins resolution silently.
On apt the conflict is settled by removing `amdgpu-install`, which
leaves its configuration files behind. The deb `postinst` detects that
state and tells the user to run `apt purge`. `dnf` and `zypper` refuse
the pair outright and remove their `%config` files on erase, so no
equivalent is needed there.

**The rpm `%post`** prints a refresh message, like the deb `postinst`.

**Nothing builds the repo packages yet.** The script writes finished
packages to `--dest-dir` and is run once per profile and stream. The
workflow that calls it is in #7280; until then it is invoked by hand, as
the documentation describes. Each format needs its own tooling,
`dpkg-buildpackage` for deb and `rpmbuild` for rpm, so the CI job builds
each profile in a container that has it.

**`build_tools/.gitignore` is deleted.** Its only entry was an
unanchored `repo` pattern, which would hide the entire new
`template/repo/` directory from git. The tool it guarded was removed in
#128.

### Also in this PR: the `rc` stream

This also builds the `rc` stream, which goes beyond the review feedback.

RFC0012 requires `stable` and `rc` at v1, while `nightly`, `weekly` and
`dev` come at v2. The tool used to build `stable` and `nightly`, so it
shipped a v2 stream but was missing a v1 one. `rc` was left out earlier
because its repositories were empty. They aren't anymore:
`rc.repo.amd.com` now serves more packages than `stable`, is signed on
all four OS profiles, and uses the same signing key.

A few details:

- **The installed file is `amdrocm-stablerc`, not `amdrocm-rc`.**
`stablerc` is the name RFC0012 gives the rc tier, so the file uses it
while the subdomain stays `rc`. That way a later split into per-tier
packages won't need to rename a file on users' machines.
- **Versioning.** Like every stream, rc uses the version it's given. CI
passes the prerelease version of the ROCm packages (`10.1.0~pre3` on
deb, `10.1.0~rc3` on rpm), which sorts below the GA.
- **`Suites:` is `stable` for every stream.** That's what the servers
publish (`dists/stable/`, `Suite: stable`); `dists/stablerc/` returns
404.

CI wiring for `rc` is in #7280, not here.

### Notes for reviewers

- The `list-profiles` subcommand has no caller yet;
`get-repo-setup-params` in #7276 calls the `list_profiles()` function
directly. Unit tests cover both.
- The test file sits in `build_tools/tests/` rather than
`packaging/linux/tests/`, where the other tests for that module's
directory live. `build_tools/tests/` is already in `testpaths`, so these
tests run in CI from the first push, including on the Windows leg.
- The root-ownership directives in `rules.j2` and the spec match the
other ROCm packages. Building as a non-root user already produces
`root:root` without them.
- No apt pin or `priority=` is shipped; the package sits at each
manager's default. Matching `amdgpu-install`'s pin would raise this
whole CDN above the distribution's own repositories. The conflict
declaration, and the purge the `postinst` points at, address the overlap
instead. Happy to add a pin if you would prefer one.
- `weekly` and `dev` aren't built: `weekly` has no packages yet, and
`dev` isn't for end users.
- Switching streams on Debian/Ubuntu isn't handled in the package yet:
the old `.sources` stays behind, so the docs say to purge first. A
follow-up will handle it in the package before it's published.

## Test Plan

All testing ran in containers.

- Built the package for all four OS profiles and all three streams with
the versions CI computes, and inspected each payload with `dpkg-deb -c`
or `rpm -qp`.
- Checked with real `dpkg` and `rpm` that each stream's package sorts
the same way as the ROCm packages in its repository.
- Installed each package on `ubuntu2404`, `rhel8`, `rhel10` and
`sles16`, refreshed metadata against the live stable, rc and nightly
repositories, and resolved the `amdrocm` package.
- Tested the `amdgpu-install` conflict in both directions on both
formats, using the real `amdgpu-install` 31.40 and 31.50 packages, then
repeated it on each format with the declaration removed as a control.
- Installed the deb against all four `amdgpu-install` states: not
installed, installed, removed but not purged, and purged.
- Tested the key check with real gpg: the real key alone, and with a
throwaway extra key. Built with a key URL that returns 404 and with one
that serves something other than a key.
- Tested switching streams in every direction on apt, dnf and zypper.
- Ran the documented install, verification and build commands.
- Ran the unit tests with the module's path handling bound to the
Windows flavour.
- Ran `pre-commit`, `shellcheck`, and the full unit test suite.

## Test Result

| Check | Result |
| --- | --- |
| New unit tests | 183 pass. Tests that need `dpkg`, `rpm`, `gpg` or the
de_DE locale skip where those are missing. |
| Full suite | No new failures. The delta is exactly the new test file.
|
| Package payload | Correct on all four profiles. Every file lands at
its documented path, owned by `root:root`. |
| Version ordering | Each stream's package sorts the same way as the
ROCm packages in its repository. At the same ROCm version, stable is
above rc and nightly on both formats. |
| Signing key | The real key is accepted. The real key plus another key,
in either order, is rejected. A key URL that returns 404 fails on the
first attempt, and a response that isn't a key fails with gpg's own
message. |
| Live repository | Metadata refreshes for all three streams on all four
profiles, and `amdrocm` resolves. A real signed ROCm package installs on
each profile using the shipped key. |
| Documented commands | All ran as written. `zypper` needs
`--allow-unsigned-rpm` for a locally built file, which the documentation
states. |
| Conflict enforcement | `dpkg`, `dnf` and `zypper` refuse the pair in
both directions. `apt` resolves it by removing the installed package. |
| Conflict controls | With the declaration removed, both packages
install together and two then own competing repository files. |
| `postinst` | Warns only in the removed-but-not-purged state, including
after `apt` removes `amdgpu-install` to resolve the conflict, and never
fails an install. |
| Stream switching | `dnf` switches directly. `apt` needs a purge first
and `zypper` needs `--oldpackage`, as documented. |
| Windows path handling | Binding the Windows flavour makes the keyring
test fail, so a regression there would be caught. |
| Template naming | The spec template is picked by package name. A
template that doesn't match the package name fails 11 unit tests and the
rpm build. |
| `pre-commit`, `shellcheck` | Clean. |

## Submission Checklist

- [x] Look over the contributing guidelines at
https://github.com/ROCm/TheRock/blob/main/CONTRIBUTING.md.

---------

Co-authored-by: Nirmal Unnikrishnan <71024015+nunnikri@users.noreply.github.com>
Base automatically changed from shared/amdrocm-repo-publish to main October 22, 2026 19:13
lsudarsh-amd added a commit that referenced this pull request Oct 22, 2026
…all test (#7276)

## Motivation

The `amd-repo-setup` bootstrap package needs a few pieces before CI can
build and ship it. This pull request adds:
- the build parameters the packaging workflow reads;
- a script that uploads a built package to the run's artifacts bucket;
- an install-harness mode that installs the built package.

The workflow job that runs them comes in the next pull request, and
publishing to repo.amd.com in the one after.

It also resolves two review comments on #6901, which this split
supersedes. The #6901 publish helper imported `boto3` inline and did not
use `WorkflowOutputRoot`. Its replacement here resolves the run's bucket
and prefix through `WorkflowOutputRoot` and writes through
`StorageBackend`. That takes `boto3` out of the script entirely, rather
than hoisting the import to the top of the file. The dependency still
exists behind `S3StorageBackend`, but the script no longer touches it.

This is the third of five pull requests:

- PR 0 (#7004) added `packaging/linux/tests` to `testpaths`, so the
tests the later ones add actually run in CI.
- PR 1 (#7269) adds the builder, its templates, its unit tests and the
user documentation.
- PR 2 (this one) adds the build parameters (`get-repo-setup-params`),
the upload to the run's artifacts bucket and the install-harness package
mode.
- PR 3 (#7280) wires the CI workflow: build, inspect and upload, off by
default.
- PR 4 (#9352) adds the `WorkflowOutputRoot` location for the package
and publishes it to `v5/amd-repo-setup/` during release promotion. Those
changes to shared CI files are kept in their own PR, so they can be
reviewed on their own.

This builds on #7269's branch, because the build parameters and the
harness read the package's names and stream rules from
`build_repo_setup_package`. The diff shows only this PR's files.

Contributes to #6986.

## Technical Details

### What this adds

**Upload script.** `upload_repo_setup_package.py` takes the builder's
`--dest-dir` and uploads the single package it finds there; zero or
several is an error. It takes `--run-id`, `--tier`, `--os-profile` and
`--pkg-type`, and writes to
`{run_id}-linux/amd-repo-setup/{os_profile}/amd-repo-setup-{tier}.{pkg_type}`
in the run's artifacts bucket. After a real upload it writes the
package's https URL to `$GITHUB_OUTPUT` as `repo_setup_package_url`; a
dry run or a local staging run writes none.

The bucket and prefix come from
`WorkflowOutputRoot.from_workflow_run()`. The #6901 version took
`--bucket`, `--prefix` and `--endpoint-url`. Two consequences:

1. **The `release` line raises.** It isn't an artifact release type, so
the bucket lookup rejects it. Release uploads happen outside CI, and
every `therock-release-*` bucket has `iam_role=None`. The upload job
does not run for `release`, so the raise is a backstop.
2. **The script needs CI context.** Its job environment must have
`GITHUB_REPOSITORY`, `RELEASE_TYPE` and the workflow event payload.

The script builds the key from the root's `bucket` and `prefix`.
`WorkflowOutputRoot` gets a method for this folder in PR 4, together
with the publish step that reads it, and the script switches to it
there. The key does not change.

The layout follows the repo-packages folder in
[RFC0012](https://github.com/ROCm/TheRock/blob/main/docs/rfcs/RFC0012-Repo-Structure.md)
(`amdrepos/` there, `amd-repo-setup/` here):
- the tier is part of the file name;
- the name is fixed per tier, so the download URL stays stable;
- the per-distro folder stops one profile's package overwriting
another's.

The folder sits outside `packages/`, so release promotion, which copies
`tarballs/`, `python/` and `packages/<type>/`, does not copy it.

The script's name follows TheRock's convention (upload to the artifacts
bucket, publish to the release buckets) and names the package, to tell
it apart from `upload_package_repo.py` in the same folder, which uploads
the content repository. Nothing on `main` calls this script yet; the
workflow that does lands in #7280.

**Build parameters.** `get_url_repo_params.py get-repo-setup-params`
writes everything the build matrix needs in one `$GITHUB_OUTPUT` call:
- `os_profiles` and `images`, the matrix;
- then `stream`, `tier`, `repo_base_url`, `gpg_key_url` and
`repo_sub_folder`.

The build line and the stream are different vocabularies: `release` maps
to `stable`, `prerelease` to `rc` and `nightly` to `nightly`. This
script holds only that mapping and where each stream is served.
Everything else about a stream comes from `build_repo_setup_package`:
- the key URL is emitted only for a signed stream;
- the dated build folder only for a per-build stream;
- the tier is the stream's repo file name without its `amdrocm-` prefix,
so `rc` gives `stablerc`.

A line with no public stream (`ci`, `dev`) gets those five values empty
and still gets the matrix. rc.repo.amd.com serves all four profiles,
signed with the same key as stable, whose fingerprint matches the
builder's pin.

`build_repo_setup_package` is imported inside the subcommand rather than
at module level. The script's other subcommands run on the runner's
system Python in `test_native_linux_packages_install.yml`, in a job that
installs no Python packages.

**Install harness.** `native_linux_package_install_test.py
--repo-setup-package-dir <dir>` configures the repository by installing
the built package, instead of writing repository files directly. It
then:
- refreshes that repository;
- checks the repo file and signing key are in place;
- installs ROCm from it.

`--repo-config-only` stops before the ROCm install. Nothing in CI uses
this mode yet.

The repo id, repo file name and key paths come from
`build_repo_setup_package` (`repo_id()`, `is_signed()`,
`DEB_KEYRING_PATH`, `RPM_GPG_KEY_PATH`), so only the distro's repo
directories stay in the harness. The package itself is found by the
builder's `PACKAGE_NAME`, so the harness takes either the built file
(`amd-repo-setup_<version>_all.deb`) or the published copy
(`amd-repo-setup-<tier>.deb`). Argument parsing rejects
`--repo-setup-package-dir` unless `--release-type` names a line with a
public stream.

`packaging/linux/tests/requirements.txt` gains `jinja2>=3.0.0`, the pin
used elsewhere, because this mode imports `build_repo_setup_package`.
The harness's other modes still load without it.

### Smaller changes

**Input validation.** These values become parts of the object key, and a
value like `../..` would escape a local staging directory:
- `--run-id` must be numeric;
- `--os-profile` must be a single safe path segment;
- `--tier` must be lowercase letters and digits.

`get-repo-setup-params` checks the run id too. It goes into
`$GITHUB_OUTPUT`, where `gha_set_output` writes multi-line values with a
heredoc whose delimiter it does not check. These inputs come from CI, so
this is defence in depth.

**One fixture.** `RunTestsTestTypeTest._base_args` gains
`repo_setup_package_dir` and `repo_config_only` with their argparse
defaults, both of which `run_tests()` reads. Without them those tests
fail, since #7004 made that directory collectable.

## Test Plan

- **Unit tests** for the upload script, `get-repo-setup-params` and the
harness package mode.
- The upload tests that check what's written drive a real
`LocalStorageBackend` and assert the bytes on disk, instead of mocking
an S3 client. They spell the expected key out, so a layout change has to
be made in two places.
  - Two tests guard against the harness drifting from the package:
- one renders the package's own install manifest for every profile and
stream, and checks it contains every file the harness looks for;
- the other checks that every stream the builder knows is reachable from
exactly one build line.
- Two more check that the harness finds the package by the builder's
package name: a package with another name is ignored, and the published
copy (`amd-repo-setup-<tier>.deb`) is found.
- Three check the `GITHUB_OUTPUT` output: a real upload writes the
package's URL, and a dry run or a local staging run writes none.
- **End to end, in containers:**
- build the package for stable, rc and nightly on ubuntu2404, rhel8,
rhel10 and sles16, then run the harness in package mode against the live
repositories, from a venv built only from
`packaging/linux/tests/requirements.txt`;
- replay #7280's stage job (`get-repo-setup-params`, build, inspect,
upload) on each distro, with MinIO standing in for S3;
  - run release promotion on the result.
- **Full suite.** Compared by test ID against the base branch.
- **Negative controls.** Break each thing a new test guards, confirm the
test fails, restore.
- **Lint.** `pre-commit`.

## Test Result

| Check | Result |
|---|---|
| Unit tests | Pass. 298 tests in the three changed test modules, 131 of
them new. `Unit Tests` passes on `ubuntu-26.04` and `windows-2022`. |
| End to end | **Harness:** 12/12 pass. An earlier revision hardcoded
the repo id `amdrocm` and failed 9/9 on ubuntu2404, rhel10 and sles16
(dnf `Unknown repo: 'amdrocm'`, zypper `Repository 'amdrocm' not found`,
no `amdrocm.sources` on deb). With the new `jinja2` line removed,
package mode fails with `No module named 'jinja2'`. **Stage replay:**
8/8, each writing the package's URL to `GITHUB_OUTPUT`, including the rc
key fetch and a check of the live index. Each uploaded object is
byte-identical to the built package, at the expected key, with nothing
else in its folder. **Promotion:** publishes the content repositories
and nothing from `amd-repo-setup/`. |
| Full suite | Compared by test ID with the base branch: 0 status
changes, 131 tests added, none removed. In CI, 3216 passed and 9 skipped
on Linux, 3138 passed and 87 skipped on Windows. |
| Negative controls | Pass. Each of these fails its tests: a hardcoded
repo id; a missing `prerelease` mapping; a module-level builder import
in either script; a build folder chosen by stream name; a key check that
ignores signing; an upload that takes the first of several packages or
searches recursively; an unvalidated tier; a tier taken from the stream
name; a package lookup that ignores the builder's package name, or that
misses the published file name; an upload URL output that's missing
after a real upload, or written without one; and a changed upload key
(folder or tier). |
| Lint | Pass |

## Submission Checklist

- [x] Look over the contributing guidelines at
https://github.com/ROCm/TheRock/blob/main/CONTRIBUTING.md.

---------

Co-authored-by: Nirmal Unnikrishnan <71024015+nunnikri@users.noreply.github.com>
@lsudarsh-amd
lsudarsh-amd merged commit 38f3bf8 into main Oct 22, 2026
64 of 76 checks passed
@lsudarsh-amd
lsudarsh-amd deleted the shared/amdrocm-repo-ci branch October 22, 2026 19:15
@github-project-automation github-project-automation Bot moved this from TODO to Done in TheRock Triage Oct 22, 2026
# skipped job's outputs are empty strings, which fromJSON cannot parse.
# Fall back to an empty matrix so this job skips cleanly.
os_profile: ${{ fromJSON(needs.resolve_repo_setup_params.outputs.os_profiles || '[]') }}
runs-on: ubuntu-26.04

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This github-hosted runner won't be able to upload packages on PRs from forks, see #9579. I'm fixing in #9581

ScottTodd added a commit that referenced this pull request Oct 30, 2026
## Motivation

* Fixes #9579
* Follow-up to #7280
* Follow-up to #9370

See the documentation at
https://github.com/ROCm/TheRock/blob/main/docs/development/s3_buckets.md#authentication.
Workflows need credentials to upload to our S3 buckets. These can come
from two sources:
1. Non-fork PRs use OIDC
2. Fork PRs use runner base credentials **so they must be self-hosted
runners**

We want to enable debugging with github-hosted runners when this
organization's self-hosted runners are unavailable (see
https://github.com/ROCm/TheRock/blob/main/docs/development/github_actions_debugging.md#working-effectively-from-forks).
For that, the workflows support building packages without uploading them
using github-hosted runners.

## Test Plan

* Run the new unit test before/after
    ```
pytest
build_tools\github_actions\tests\workflow_self_hosted_runners_test.py
    
E AssertionError: Jobs using configure_aws_artifacts_credentials must
allow self-hosted runners with baseline storage credentials for fork
PRs.
E Use a self-hosted runner label or a conditional runner input, as in
multi_arch_build_native_linux_packages.yml.
    E           See docs/development/s3_buckets.md#authentication.
E - multi_arch_build_native_linux_packages.yml:
jobs.stage_repo_setup_package: runs-on: 'ubuntu-26.04'
E - multi_arch_build_native_windows_packages.yml: jobs.build: runs-on:
'windows-2022'
    

build_tools\github_actions\tests\workflow_self_hosted_runners_test.py:69:
AssertionError
    ```
* Watch CI runs on PRs from forks

## Submission Checklist

- [x] Look over the contributing guidelines at
https://github.com/ROCm/TheRock/blob/main/CONTRIBUTING.md.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

6 participants