Skip to content

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

Open
lsudarsh-amd wants to merge 6 commits into
shared/amdrocm-repo-publishfrom
shared/amdrocm-repo-ci
Open

lsudarsh-amd wants to merge 6 commits into
shared/amdrocm-repo-publishfrom
shared/amdrocm-repo-ci

Conversation

@lsudarsh-amd

Copy link
Copy Markdown
Contributor

Motivation

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

This pull request wires the package into the existing native package pipeline, so that it is built and checked on CI runs, built for real on the released lines, and uploaded alongside the native content packages.

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, because the new unit test and the new CI step both call build_repo_package.py, which #7269 adds. therock-pr-bot waits on those checks, so it fails too.

Closes #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 Runs on
setup_repo_params Resolves the profile matrix, per-profile container image, repository URL and nightly sub-folder prerelease, nightly, release
stage_repo_package Builds the real package per profile and uploads it as an artifact same, continue-on-error
publish_repo_package Uploads it to <run_id>-linux/packages/<format>/repo/<os_profile>/, beside the content packages but outside their index same minus release, continue-on-error

The release line has no artifacts bucket, so publishing is skipped there. The other two are continue-on-error, so this package cannot fail a release build.

Build and inspect step

A new step in build_native_packages builds each OS profile twice, unsigned and signed, and asserts the payload: expected files, at expected paths, owned by root, with the signing key only at its current path. It runs offline against a placeholder URL. The logic is in build_tools/packaging/linux/inspect_repo_package.py.

Three things the diff does not make obvious:

  • ci line only. build_native_packages is on the release path, so a gate that can fail the job would otherwise let this package stop a release. The released lines build for real in stage_repo_package, which cannot block.
  • Inspects, not installs. One Ubuntu container, no matrix, no dnf or zypper. Per-distro installs belong in test_native_linux_packages_install.yml, which is blocked on its GPU runner and a required package_install_url.
  • Both signing forms. They install different files, and the signed build is the only CI check on the key paths, which were renamed to avoid a file conflict with the amdgpu driver packages. It uses a throwaway key via --gpg-key-file, so no key is fetched.

enable_repo_package, a workflow_call boolean defaulting to true, gates the three jobs and the step. Existing callers are unchanged.

Reads of a published repository

stage_repo_package keeps --verify-repo-url and fetches the repository index on the prerelease and release lines. It catches a package that installs but cannot refresh, and those runs publish to the line they read. Nightly is skipped, because its dated folder is published by the same run.

No pull request reaches a published repository. ci has no entry in _PUBLIC_REPO_BASE_URLS, so repo_base_url is empty and stage_repo_package skips. The earlier test_repo_package job, which refreshed against live prerelease on every pull request, is deleted.

Note for reviewers

That job was the only CI caller of PR 2's (#7276) --repo-package-dir and --repo-config-only. They keep their unit tests and gain a CI caller in the follow-up above.

Test Plan

The branch cannot test itself yet, because build_repo_package.py belongs to PR 1 (#7269). Verification used a local scratch tree with PR 1 and PR 2 merged in, entirely in throwaway containers.

What it proves How
The new script is correct Unit tests over path derivation, both output parsers and the payload check, using real dpkg-deb -c and rpm -qp output as fixtures
Nothing else regressed Full build_tools suite as CI runs it, diffing failure names with and without this pull request, not counts
The step is offline Run for both package types with --network none
The checks can fail Negative controls: old key file names restored, repository file dropped, ownership changed, two packages left where publish expects one
The packages install All eight installed by hand on ubuntu:24.04, redhat/ubi8, ubi10 and bci-base:16.0
The Windows leg is safe Unit tests re-run with the module bound to Windows path semantics
CI hygiene actionlint, pre-commit, and the unit tests that walk the workflow directory

Test Result

Everything passes. The rows below match the table above.

What it proves Result
The new script is correct 41 unit tests passed
Nothing else regressed 1975 passed, and the 8 failures are identical by name with and without this pull request. scan_tools 31 passed, test_tools 49 passed
The step is offline 8 builds passed with no network, covering four profiles unsigned and signed
The checks can fail Every negative control fired, and undoing each one returned the check to passing
The packages install 8 of 8
The Windows leg is safe 27 of the 41 unit tests fail when the module is bound to Windows path semantics
CI hygiene actionlint, pre-commit and the 23 workflow tests passed

The manual install confirmed three things payload inspection cannot. The deb keyring is dearmored binary at mode 0644, which apt requires when a source pins Signed-By. The rpm key is ASCII armored and rpm --import accepts it. Removing the package cleans up every file it installed.

Four things cannot be verified outside GitHub Actions and are worth watching on the first CI run:

  1. Whether a skipped setup_repo_params produces clean skips in the jobs reading its outputs. Those reads are guarded with the pattern already used elsewhere in this repository, but actionlint cannot check the behaviour either way.
  2. stage_repo_package and publish_repo_package running for real, including the AWS credentials action and the upload.
  3. A refresh against a real repository. The packages built here point at a placeholder URL.
  4. The windows-2022 leg, which was simulated locally rather than run.

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.

@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.

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.
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
Comment on lines +106 to +114
- name: Checking out repository
timeout-minutes: 15
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
repository: ${{ inputs.repository || github.repository }}
ref: ${{ inputs.ref || '' }}

- name: Set up Python
@lsudarsh-amd
lsudarsh-amd changed the base branch from main to shared/amdrocm-repo-publish September 30, 2026 19:11
@lsudarsh-amd
lsudarsh-amd added this pull request to stack #8646 September 30, 2026 19:12
@lsudarsh-amd
lsudarsh-amd marked this pull request as ready for review September 30, 2026 23:10
@lsudarsh-amd

Copy link
Copy Markdown
Contributor Author

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

@ScottTodd thanks for the review and the pointers. I've stacked the PRs so each diff only shows its own changes (this one sits on #7276, which sits on #7269), and with that the unit tests and package builds pass here too. I've marked this ready for review.

I've pushed fixes for all five of your comments. To quickly sum up:

  • one get-repo-params call
  • the inspect step runs on every line
  • failures now fail the run
  • the package is published to S3 in one job instead of through artifacts
  • the inline shell moved into a script

Also, prerelease now publishes the rc package, ASAN builds skip it, and the AWS credentials are only set up after the package is built.

zizmor flags this workflow's existing :latest image, same as on every PR that touches this file. When you have a chance, could you please take a look at these changes?

@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.

GitHub Actions workflow code looks reasonable enough now. Will defer the rest of the review to native linux packaging codeowners.

Comment on lines +298 to +299
container:
image: ${{ fromJSON(needs.setup_repo_params.outputs.images || '{}')[matrix.os_profile] }}

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 suppress the zizmor unpinned-images audit finding here: https://github.com/ROCm/TheRock/actions/runs/36768360954/job/110068432039?pr=7280#step:6:95

error[unpinned-images]: unpinned image references
   --> .scan-target/.github/workflows/multi_arch_build_native_linux_packages.yml:299:7
    |
299 |       image: ${{ fromJSON(needs.setup_repo_params.outputs.images || '{}')[matrix.os_profile] }}
    |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ container image may be unpinned
    |
    = note: audit confidence → Low
    = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-images

See #8684 for example:

    container:
-      image: ${{ needs.prepare_install_context.outputs.container_image }}
+      # get_url_repo_params.py manages image references for each OS profile.
+      # zizmor cannot inspect the dynamically selected image.
+      image: ${{ needs.prepare_install_context.outputs.container_image }} # zizmor: ignore[unpinned-images]

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.

Done, I've suppressed it the same way as #8684. While in this part of the code, I noticed that #8637 moved id-token: write from the workflow level to the jobs that need it, so I also gave this job its own contents: read / id-token: write. Otherwise it would lose the id-token permission it needs for AWS now that the stack includes main.

…tage

- Suppress unpinned-images on the stage job's image, same as #8684.
- Give the stage job its own id-token: write. #8637 moved it off the
  workflow level, so the job would lose AWS access once main is merged.
- Fix the inspector docstring: unit tests pin the rc filename, not the
  build step.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: TODO

Development

Successfully merging this pull request may close these issues.

Add amdrocm-repo package for ROCm repository setup

4 participants