Skip to content

ART-21677: Push .repo files to public upstream for RHCOS images - #3179

Open
locriandev wants to merge 4 commits into
openshift-eng:mainfrom
locriandev:feature/push-repo-files-to-public
Open

ART-21677: Push .repo files to public upstream for RHCOS images#3179
locriandev wants to merge 4 commits into
openshift-eng:mainfrom
locriandev:feature/push-repo-files-to-public

Conversation

@locriandev

@locriandev locriandev commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Add support for pushing generated .repo files (art-signed.repo and art-unsigned.repo) to the public upstream repository (https://github.com/openshift/os) during RHCOS image rebase operations.

Background

The upstream team does not have access to openshift-priv where .repo files are currently stored. They need these files in the public upstream repository to properly configure yum repositories during image builds.

Changes

Affected Images

  • rhcos-node-image
  • rhcos-node-image-rhel10
  • rhcos-node-extensions
  • rhcos-node-extensions-rhel10

All these images have git@github.com:openshift-priv/os.git as source URL and https://github.com/openshift/os as public web URL.

Workflow

  1. Generate .repo files and commit to openshift-priv/os (existing behavior)
  2. Check if .repo files changed in the commit
  3. If unchanged, check if branch exists on public upstream via git ls-remote
  4. If changed OR branch doesn't exist:
    • Clone public upstream via HTTPS
    • Add SSH remote for push operations
    • Create or checkout orphan branch
    • Copy .repo files to repository root
    • Commit and push to public upstream via SSH

Testing

https://art-jenkins.apps.prod-stable-spoke1-dc-iad2.itup.redhat.com/job/aos-cd-builds/job/build%252Focp4-konflux/41119/ created https://github.com/openshift/os/tree/art-repos-rhcos-node-extensions-5.0 with the generated .repo files in it.

ART has been asked to push the generated .repo files (art-signed.repo
and art-unsigned.repo) to the public upstream repository
(https://github.com/openshift/os) during rebase operations. This is
needed because the upstream team does not have access to openshift-priv
where these files are currently stored.

This change adds:
- New method _push_repo_files_to_public_upstream() that pushes .repo
  files to public upstream for RHCOS images
- Optimization to skip push if files haven't changed and branch exists
- Branch naming convention: art-repos-{IMAGE_NAME}-{MAJOR}.{MINOR}
- Only affects these RHCOS images:
  - rhcos-node-image
  - rhcos-node-image-rhel10
  - rhcos-node-extensions
  - rhcos-node-extensions-rhel10

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

rh-pre-commit.version: 2.4.0
rh-pre-commit.check-secrets: ENABLED
The previous implementation used the HTTPS URL from the 'web' field,
which fails during git push with authentication errors. Converting
to SSH format (git@github.com:org/repo.git) enables proper SSH
key authentication for push operations.

Changes:
- Convert HTTPS URL to SSH format before cloning
- Use SSH URL for both ls-remote check and clone operations
- Format: https://github.com/org/repo -> git@github.com:org/repo.git

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

rh-pre-commit.version: 2.4.0
rh-pre-commit.check-secrets: ENABLED
The git_clone helper automatically converts SSH URLs to HTTPS by design
(to avoid pushing to public openshift repos). This causes authentication
failures when trying to push.

Solution: Clone with HTTPS (default behavior), then add a separate SSH
remote specifically for push operations. This allows:
- Clone via HTTPS (read-only, works with existing auth)
- Push via SSH (write access, uses SSH keys)

Changes:
- Clone using web_url (HTTPS, gets converted by git_clone)
- Add 'push_remote' with SSH URL after cloning
- Push to 'push_remote' instead of 'origin'

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

rh-pre-commit.version: 2.4.0
rh-pre-commit.check-secrets: ENABLED
The branch should contain only the .repo files without any parent
commits or other files from the upstream repository. This keeps
the branch clean and minimal.

Changes:
- Use --orphan flag when creating new branch
- Run 'git rm -rf .' to clear all staged files from orphan branch
- Only add the two .repo files to the orphan branch

Result: Branch contains only art-signed.repo and art-unsigned.repo

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

rh-pre-commit.version: 2.4.0
rh-pre-commit.check-secrets: ENABLED
@openshift-ci

openshift-ci Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign joepvd for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

The rebaser now propagates RHCOS .repo files from the build repository to computed branches in openshift/os, with conditional Git operations and tests covering branch naming, image filtering, and synchronization behavior.

Changes

Public RHCOS repository synchronization

Layer / File(s) Summary
RHCOS repository propagation
doozer/doozerlib/backend/rebaser.py
KonfluxRebaser computes upstream branch names, detects qualifying RHCOS images, conditionally clones or creates branches, copies .repo files, and commits and pushes them to openshift/os after the build repository rebase.
Synchronization behavior tests
doozer/tests/backend/test_rebaser.py
Tests cover branch naming, invalid group names, non-RHCOS filtering, mocked Git operations, and branch format consistency.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant KonfluxRebaser
  participant BuildRepository
  participant OpenshiftOS
  KonfluxRebaser->>BuildRepository: push rebase commit
  KonfluxRebaser->>BuildRepository: check .oit/*.repo changes
  KonfluxRebaser->>OpenshiftOS: check branch and clone upstream
  KonfluxRebaser->>OpenshiftOS: copy art-signed.repo and art-unsigned.repo
  KonfluxRebaser->>OpenshiftOS: commit and push branch
Loading

Suggested reviewers: ashwindasr, lgarciaaco, fgallott

🚥 Pre-merge checks | ✅ 10 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Ai-Attribution ⚠️ Warning PR text discloses Claude Code, but the PR commits use Co-Authored-By for AI assistance instead of Assisted-by/Generated-by. Replace AI Co-Authored-By trailers on the PR commits with Red Hat Assisted-by or Generated-by trailers, and remove any AI co-author lines.
✅ Passed checks (10 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
No-Weak-Crypto ✅ Passed Diff only adds RHCOS repo-push logic; searches found no MD5/SHA1/DES/RC4/3DES/Blowfish/ECB, custom crypto, or secret comparisons.
Container-Privileges ✅ Passed PR touches only Python code/tests; no container or K8s manifests and no privileged/host*/*Escalation settings appear in changed files.
No-Sensitive-Data-In-Logs ✅ Passed New logs only include public repo URLs, branch names, and image keys; no passwords, tokens, API keys, PII, or internal hostnames were introduced.
No-Hardcoded-Secrets ✅ Passed No hardcoded secrets, embedded credentials, or secret-like literals were added in the changed code; the new strings are URLs, branch names, and commit text only.
No-Injection-Vectors ✅ Passed The new push path uses argv-style git helpers and yaml.safe_dump; no shell=True, eval/exec, os.system, pickle.loads, yaml.load, or dangerous HTML use appears.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: pushing .repo files to the public upstream for RHCOS images.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@locriandev locriandev changed the title Push .repo files to public upstream for RHCOS images ART-21677: Push .repo files to public upstream for RHCOS images Jul 17, 2026
@openshift-ci-robot openshift-ci-robot added the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label Jul 17, 2026
@openshift-ci-robot

openshift-ci-robot commented Jul 17, 2026

Copy link
Copy Markdown

@locriandev: This pull request references ART-21677 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "5.0.0" version, but no target version was set.

Details

In response to this:

Summary

Add support for pushing generated .repo files (art-signed.repo and art-unsigned.repo) to the public upstream repository (https://github.com/openshift/os) during RHCOS image rebase operations.

Background

The upstream team does not have access to openshift-priv where .repo files are currently stored. They need these files in the public upstream repository to properly configure yum repositories during image builds.

Changes

Affected Images

  • rhcos-node-image
  • rhcos-node-image-rhel10
  • rhcos-node-extensions
  • rhcos-node-extensions-rhel10

All these images have git@github.com:openshift-priv/os.git as source URL and https://github.com/openshift/os as public web URL.

Implementation

  • New method: _push_repo_files_to_public_upstream() in KonfluxRebaser class
  • Branch naming: art-repos-{IMAGE_NAME}-{MAJOR}.{MINOR} (e.g., art-repos-rhcos-node-image-5.0)
  • Orphan branches: Branches contain only the two .repo files with no parent commits
  • Optimization: Only pushes if files have changed or branch doesn't exist yet
  • Authentication: Uses SSH for push operations after cloning via HTTPS

Key Features

  1. Filtering: Only processes RHCOS images (checks content.source.git.web == https://github.com/openshift/os)
  2. Change detection: Uses git diff to check if .repo files changed in rebase commit
  3. Branch existence check: Uses git ls-remote to avoid unnecessary clone operations
  4. Orphan branch creation: Creates clean branches with only .repo files using --orphan flag
  5. SSH push: Adds separate SSH remote for push operations to avoid HTTPS authentication issues

Workflow

  1. Generate .repo files and commit to openshift-priv/os (existing behavior)
  2. Check if .repo files changed in the commit
  3. If unchanged, check if branch exists on public upstream via git ls-remote
  4. If changed OR branch doesn't exist:
  • Clone public upstream via HTTPS
  • Add SSH remote for push operations
  • Create or checkout orphan branch
  • Copy .repo files to repository root
  • Commit and push to public upstream via SSH

Testing

  • ✅ All 85 rebaser tests pass
  • ✅ 5 new unit tests covering filtering, branch naming, and optimization
  • ✅ Lint checks pass
  • ✅ Tested end-to-end with OCP 5.0, rhcos-node-image
  • ✅ Verified orphan branch creation with only .repo files

Test Plan

doozer --group openshift-5.0 \
 images:rebase-konflux \
 --images rhcos-node-image \
 --push

Expected: .repo files pushed to art-repos-rhcos-node-image-5.0 branch in https://github.com/openshift/os

🤖 Generated with Claude Code

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci

openshift-ci Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

@locriandev: The following test failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
ci/prow/security f04466e link false /test security

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
doozer/tests/backend/test_rebaser.py (1)

2250-2307: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Exercise and assert the synchronization paths directly.

The negative test has no “Git not called” assertions, while the positive test aborts at cloning and only checks .called. It therefore does not verify ls-remote, orphan checkout, two-file staging, commit/push, or the existing-branch skip path. Add explicit awaited-call assertions and at least one successful end-to-end mocked synchronization.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@doozer/tests/backend/test_rebaser.py` around lines 2250 - 2307, Expand the
tests around _push_repo_files_to_public_upstream to assert that non-RHCOS inputs
make no Git calls, and that the RHCOS path awaits ls-remote, orphan checkout,
stages both expected files, commits, and pushes. Replace the clone-failure-only
positive test with successful mocked synchronization, and add coverage for an
existing remote branch that skips synchronization; use explicit awaited-call
assertions on the relevant git_helper symbols.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@doozer/doozerlib/backend/rebaser.py`:
- Around line 1991-2008: Update the rebase flow around git_helper.git_clone so
the potentially long clone executes asynchronously without blocking the event
loop, using an async subprocess or exectools.to_thread; preferably limit the
operation to the required branch with a shallow fetch. Preserve the existing
HTTPS clone behavior, error logging, and exception propagation.
- Around line 235-237: Update _push_repo_files_to_public_upstream and its
related logic to compare the two generated .repo files directly against the
public branch rather than using HEAD~1..HEAD. Validate the diff, ls-remote, and
commit command results, treating only an explicit no-difference result as a
no-op; propagate all other Git failures so retries cannot silently skip
synchronization.
- Around line 2018-2029: Update the synchronization logic around the repo_files
loop to validate that both art-signed.repo and art-unsigned.repo source files
exist before copying or staging either file. If either is missing, fail the
synchronization immediately and atomically instead of continuing; only perform
shutil.copy2 and git add after all required sources pass validation.

---

Nitpick comments:
In `@doozer/tests/backend/test_rebaser.py`:
- Around line 2250-2307: Expand the tests around
_push_repo_files_to_public_upstream to assert that non-RHCOS inputs make no Git
calls, and that the RHCOS path awaits ls-remote, orphan checkout, stages both
expected files, commits, and pushes. Replace the clone-failure-only positive
test with successful mocked synchronization, and add coverage for an existing
remote branch that skips synchronization; use explicit awaited-call assertions
on the relevant git_helper symbols.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: openshift-eng/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: e6f892ef-02c3-41bb-aa66-a90047fa583f

📥 Commits

Reviewing files that changed from the base of the PR and between 1e69679 and f04466e.

📒 Files selected for processing (2)
  • doozer/doozerlib/backend/rebaser.py
  • doozer/tests/backend/test_rebaser.py

Comment on lines +235 to +237
# Push .repo files to public upstream for RHCOS images
# This is called AFTER the push to ensure the commit exists and we can check if files changed
await self._push_repo_files_to_public_upstream(metadata, build_repo)

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.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Base synchronization decisions on public-branch content, not the latest local commit.

The build repository is pushed first. If the public push then fails, a retry can create an empty commit; HEAD~1..HEAD reports no changes and the existing public branch is skipped indefinitely. Git failures are also conflated with expected states because the diff, ls-remote, and commit return codes are not validated.

Compare the two generated files against the public branch and treat only an explicit no-diff result as a no-op; propagate all other Git failures.

Also applies to: 1949-1978, 2031-2044

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@doozer/doozerlib/backend/rebaser.py` around lines 235 - 237, Update
_push_repo_files_to_public_upstream and its related logic to compare the two
generated .repo files directly against the public branch rather than using
HEAD~1..HEAD. Validate the diff, ls-remote, and commit command results, treating
only an explicit no-difference result as a no-op; propagate all other Git
failures so retries cannot silently skip synchronization.

Comment on lines +1991 to +2008
# Clone the PUBLIC upstream repository to a temporary directory
# Note: git_clone will convert SSH to HTTPS by default (by design)
with TemporaryDirectory() as temp_dir:
upstream_dir = Path(temp_dir) / "upstream"

try:
# Clone using HTTPS (git_clone converts SSH to HTTPS automatically)
git_helper.git_clone(web_url, str(upstream_dir))
except Exception as e:
self._logger.error(f"Failed to clone public upstream repo {web_url}: {e}")
raise

# Create or checkout the branch
with exectools.Dir(str(upstream_dir)):
# Add SSH remote for pushing (we don't want to push to openshift/ via HTTPS)
self._logger.info(f"Adding SSH remote for push: {ssh_url}")
exectools.cmd_assert(["git", "remote", "add", "push_remote", ssh_url])

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.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Move the synchronous clone off the async event loop.

git_clone performs a potentially long full clone of openshift/os synchronously, blocking other concurrent rebases. Use an async subprocess or exectools.to_thread, preferably with a branch-specific shallow fetch.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@doozer/doozerlib/backend/rebaser.py` around lines 1991 - 2008, Update the
rebase flow around git_helper.git_clone so the potentially long clone executes
asynchronously without blocking the event loop, using an async subprocess or
exectools.to_thread; preferably limit the operation to the required branch with
a shallow fetch. Preserve the existing HTTPS clone behavior, error logging, and
exception propagation.

Comment on lines +2018 to +2029
# Copy .repo files from build repo to upstream repo root
repo_files = ["art-signed.repo", "art-unsigned.repo"]
for repo_file in repo_files:
src_path = build_repo.local_dir / ".oit" / repo_file
dst_path = upstream_dir / repo_file

if not src_path.exists():
self._logger.warning(f"Source file not found: {src_path}")
continue

shutil.copy2(src_path, dst_path)
exectools.cmd_assert(["git", "add", repo_file])

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Require both .repo files before staging the update.

Continuing after a missing source file can create an incomplete orphan branch or retain a stale file on an existing branch. Validate both files up front and fail the synchronization atomically.

Proposed fix
 repo_files = ["art-signed.repo", "art-unsigned.repo"]
+missing_files = [
+    repo_file
+    for repo_file in repo_files
+    if not (build_repo.local_dir / ".oit" / repo_file).is_file()
+]
+if missing_files:
+    raise FileNotFoundError(
+        f"Required public repo files are missing: {', '.join(missing_files)}"
+    )
+
 for repo_file in repo_files:
     src_path = build_repo.local_dir / ".oit" / repo_file
     dst_path = upstream_dir / repo_file
-
-    if not src_path.exists():
-        self._logger.warning(f"Source file not found: {src_path}")
-        continue
 
     shutil.copy2(src_path, dst_path)
     exectools.cmd_assert(["git", "add", repo_file])
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Copy .repo files from build repo to upstream repo root
repo_files = ["art-signed.repo", "art-unsigned.repo"]
for repo_file in repo_files:
src_path = build_repo.local_dir / ".oit" / repo_file
dst_path = upstream_dir / repo_file
if not src_path.exists():
self._logger.warning(f"Source file not found: {src_path}")
continue
shutil.copy2(src_path, dst_path)
exectools.cmd_assert(["git", "add", repo_file])
# Copy .repo files from build repo to upstream repo root
repo_files = ["art-signed.repo", "art-unsigned.repo"]
missing_files = [
repo_file
for repo_file in repo_files
if not (build_repo.local_dir / ".oit" / repo_file).is_file()
]
if missing_files:
raise FileNotFoundError(
f"Required public repo files are missing: {', '.join(missing_files)}"
)
for repo_file in repo_files:
src_path = build_repo.local_dir / ".oit" / repo_file
dst_path = upstream_dir / repo_file
shutil.copy2(src_path, dst_path)
exectools.cmd_assert(["git", "add", repo_file])
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@doozer/doozerlib/backend/rebaser.py` around lines 2018 - 2029, Update the
synchronization logic around the repo_files loop to validate that both
art-signed.repo and art-unsigned.repo source files exist before copying or
staging either file. If either is missing, fail the synchronization immediately
and atomically instead of continuing; only perform shutil.copy2 and git add
after all required sources pass validation.

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

Labels

jira/valid-reference Indicates that this PR references a valid Jira ticket of any type.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants