Skip to content

ART-14708: Auto-sync golang builders to layered operator branches#2814

Draft
rayfordj wants to merge 2 commits into
openshift-eng:mainfrom
rayfordj:ART-14708
Draft

ART-14708: Auto-sync golang builders to layered operator branches#2814
rayfordj wants to merge 2 commits into
openshift-eng:mainfrom
rayfordj:ART-14708

Conversation

@rayfordj
Copy link
Copy Markdown
Contributor

Add golang_sync_branches support to the update-golang pipeline. When golang is bumped for an OCP version, PRs are automatically created for layered operator branches (logging, oadp, etc.) listed in group.yml vars.golang_sync_branches.

Made-with: Cursor

Signed-off-by: Rayford Johnson rayfordj@users.noreply.github.com

rh-pre-commit.version: 2.3.2
rh-pre-commit.check-secrets: ENABLED

Add golang_sync_branches support to the update-golang pipeline. When
golang is bumped for an OCP version, PRs are automatically created for
layered operator branches (logging, oadp, etc.) listed in group.yml
vars.golang_sync_branches.

Made-with: Cursor

Signed-off-by: Rayford Johnson <rayfordj@users.noreply.github.com>

rh-pre-commit.version: 2.3.2
rh-pre-commit.check-secrets: ENABLED
@openshift-ci-robot openshift-ci-robot added the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label Apr 23, 2026
@openshift-ci-robot
Copy link
Copy Markdown

openshift-ci-robot commented Apr 23, 2026

@rayfordj: This pull request references ART-14708 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:

Add golang_sync_branches support to the update-golang pipeline. When golang is bumped for an OCP version, PRs are automatically created for layered operator branches (logging, oadp, etc.) listed in group.yml vars.golang_sync_branches.

Made-with: Cursor

Signed-off-by: Rayford Johnson rayfordj@users.noreply.github.com

rh-pre-commit.version: 2.3.2
rh-pre-commit.check-secrets: ENABLED

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
Copy link
Copy Markdown
Contributor

openshift-ci Bot commented Apr 23, 2026

[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 thegreyd 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
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 23, 2026

Important

Review skipped

Auto reviews are limited based on label configuration.

🚫 Review skipped — only excluded labels are configured. (1)
  • do-not-merge/work-in-progress

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Enterprise

Run ID: 93d5e0a4-c1af-42c4-8340-8595a5bc675c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

The update_golang_streams function now returns replacement mappings ({old_pullspec: new_pullspec}), which are applied to layered operator branches via a new update_layered_branches function that updates streams.yml, creates per-branch PRs concurrently, and reports results to Slack.

Changes

Cohort / File(s) Summary
Pipeline Implementation
pyartcd/pyartcd/pipelines/update_golang.py
Modified update_golang_streams to return pullspec replacement mappings from all builder updates. Added new update_layered_branches function that reads golang_sync_branches from group.yml, applies replacements to streams.yml on each branch, creates per-branch PRs concurrently, handles GitHub 404 errors, and posts aggregated Slack summaries. Updated pipeline run method to capture and apply these replacements.
Test Coverage
pyartcd/tests/pipelines/test_update_golang.py
Added TestUpdateLayeredBranches test suite with comprehensive coverage for early-exit scenarios (missing config, empty replacements), successful PR creation with parameter validation, skipping behavior when builders don't match, dry-run and skip-pr mode suppression, partial PR creation failures, and GitHub 404 branch-not-found error handling with Slack reporting.

Sequence Diagram

sequenceDiagram
    participant Pipeline
    participant GitHub as GitHub API
    participant Config as group.yml/<br/>streams.yml
    participant Slack

    Pipeline->>Config: Read golang_sync_branches
    Config-->>Pipeline: Return branch list
    
    loop For each branch
        Pipeline->>GitHub: Fetch streams.yml from branch
        GitHub-->>Pipeline: Return streams content
        
        Pipeline->>Pipeline: Apply pullspec replacements
        
        alt Builder matches replacement
            Pipeline->>GitHub: Create PR with updated streams.yml
            GitHub-->>Pipeline: PR created or 404 error
            Pipeline->>Slack: Log result/error
        else No match
            Pipeline->>Slack: Log skipped branch
        end
    end
    
    Pipeline->>Slack: Post aggregated summary thread
    Slack-->>Pipeline: Confirmation
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@rayfordj
Copy link
Copy Markdown
Contributor Author

/hold
/label tide/merge-method-squash

@openshift-ci openshift-ci Bot added do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. tide/merge-method-squash Denotes a PR that should be squashed by tide when it merges. labels Apr 23, 2026
@rayfordj rayfordj marked this pull request as draft April 23, 2026 01:38
@openshift-ci openshift-ci Bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Apr 23, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
pyartcd/pyartcd/pipelines/update_golang.py (1)

702-705: Remove unnecessary async decorator or use asyncio.to_thread() for true parallelism.

_update_layered_branch is declared as async but contains no await statements—it only performs synchronous PyGithub API calls. This means each coroutine runs to completion sequentially, blocking the event loop and providing no parallelism benefit.

Either:

  1. Convert _update_layered_branch to a regular function and wrap calls with asyncio.to_thread() to run them in the thread pool, or
  2. Remove the async decorator if no event loop integration is needed.

The first option enables true parallel GitHub operations across branches.

Suggested refactor (option 1)
         results = await asyncio.gather(
-            *[self._update_layered_branch(b, go_version, replacements) for b in sync_branches],
+            *[asyncio.to_thread(self._update_layered_branch, b, go_version, replacements) for b in sync_branches],
             return_exceptions=True,
         )

-    async def _update_layered_branch(
+    def _update_layered_branch(
         self, branch_name: str, go_version: str, replacements: dict[str, str]
     ) -> str | None:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pyartcd/pyartcd/pipelines/update_golang.py` around lines 702 - 705, The call
to asyncio.gather is launching coroutines for _update_layered_branch which is
marked async but contains no awaits, so it runs synchronously and blocks the
event loop; change _update_layered_branch to a regular (non-async) function and
invoke it with asyncio.to_thread in the gather call (e.g., replace
self._update_layered_branch(...) with
asyncio.to_thread(self._update_layered_branch, ...) so GitHub API calls run in
the thread pool), or alternatively remove the async decorator and call
asyncio.to_thread for true parallelism—ensure you update any references to
_update_layered_branch accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pyartcd/pyartcd/pipelines/update_golang.py`:
- Around line 702-719: The code currently collects exceptions from
asyncio.gather for _update_layered_branch but only logs and summarizes them,
allowing run() to continue as a success; change this by detecting any Exception
in results after the loop and raising an error (e.g., raise RuntimeError or the
first exception) with the built summary so the pipeline fails properly.
Concretely: after building summary (the summary variable and _LOGGER.info call),
if any item in results is an Exception, log the summary, post it via
self._slack_client.say_in_thread, then raise a RuntimeError(f"Layered branch
updates failed: {summary}") (or re-raise the first Exception) so run() observes
failure instead of proceeding. Ensure you reference _update_layered_branch,
results, summary and self._slack_client.say_in_thread in the change.
- Around line 596-598: The version comparison currently using string-split is
incorrect; change the logic that checks whether latest_go > previous_go to parse
both latest_go and previous_go into numeric tuples (e.g., split on '.' and map
to ints, padding shorter tuples with zeros) and compare those tuples instead,
then ensure that when the numeric comparison is True you populate the
replacements dict with replacements[latest_go] = new_latest_go and
replacements[previous_go] = latest_go so the GO_PREVIOUS/GO_LATEST remap is
applied for the main and layered branches; update the code paths that reference
latest_go, previous_go, replacements, and any helper currently performing the
string comparison to use the numeric-tuple comparison.

---

Nitpick comments:
In `@pyartcd/pyartcd/pipelines/update_golang.py`:
- Around line 702-705: The call to asyncio.gather is launching coroutines for
_update_layered_branch which is marked async but contains no awaits, so it runs
synchronously and blocks the event loop; change _update_layered_branch to a
regular (non-async) function and invoke it with asyncio.to_thread in the gather
call (e.g., replace self._update_layered_branch(...) with
asyncio.to_thread(self._update_layered_branch, ...) so GitHub API calls run in
the thread pool), or alternatively remove the async decorator and call
asyncio.to_thread for true parallelism—ensure you update any references to
_update_layered_branch accordingly.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ba4782a2-de7f-40d8-a375-b04c2287be75

📥 Commits

Reviewing files that changed from the base of the PR and between 0fc81be and 3c19b94.

📒 Files selected for processing (2)
  • pyartcd/pyartcd/pipelines/update_golang.py
  • pyartcd/tests/pipelines/test_update_golang.py

Comment on lines +596 to +598
replacements[latest_go] = new_latest_go
if previous_go:
replacements[previous_go] = latest_go
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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
python - <<'PY'
cases = [("1.10.1", "1.9"), ("2.0.0", "1.25")]
for go_version, go_latest in cases:
    current_expr = (
        go_version.split(".")[0] >= go_latest.split(".")[0]
        and go_version.split(".")[1] > go_latest.split(".")[1]
    )
    print(f"{go_version} vs {go_latest}: current_expr={current_expr}")
PY

Repository: openshift-eng/art-tools

Length of output: 134


🏁 Script executed:

sed -n '580,620p' pyartcd/pyartcd/pipelines/update_golang.py | cat -n

Repository: openshift-eng/art-tools

Length of output: 2728


🏁 Script executed:

sed -n '590,610p' pyartcd/pyartcd/pipelines/update_golang.py | cat -n

Repository: openshift-eng/art-tools

Length of output: 1370


🏁 Script executed:

python - <<'PY'
cases = [("1.10.1", "1.9"), ("2.0.0", "1.25"), ("1.9", "1.10"), ("1.20", "1.20")]
for go_version, go_latest in cases:
    # Original logic
    original = (
        go_version.split(".")[0] >= go_latest.split(".")[0]
        and go_version.split(".")[1] > go_latest.split(".")[1]
    )
    # Suggested fix
    suggested = tuple(map(int, go_version.split('.')[:2])) > tuple(map(int, go_latest.split('.')[:2]))
    print(f"{go_version} vs {go_latest}: original={original}, suggested={suggested}")
PY

Repository: openshift-eng/art-tools

Length of output: 248


🏁 Script executed:

python - <<'PY'
# Test edge cases for the suggested fix
cases = [
    ("1", "0"),           # Single digit
    ("1.20.5", "1.20.4"), # Patch version (should compare major.minor only)
    ("1.20.5", "1.19"),   # Patch vs no patch
]

for go_version, go_latest in cases:
    try:
        parts_v = list(map(int, go_version.split('.')[:2]))
        parts_l = list(map(int, go_latest.split('.')[:2]))
        suggested = tuple(parts_v) > tuple(parts_l)
        print(f"{go_version} vs {go_latest}: suggested={suggested}")
    except (ValueError, IndexError) as e:
        print(f"{go_version} vs {go_latest}: ERROR - {e}")
PY

Repository: openshift-eng/art-tools

Length of output: 154


🏁 Script executed:

# Verify the fix handles version strings without proper minor versions
python - <<'PY'
# Test what happens with version strings that don't have 2 components
test_cases = [
    ("1", "1.0"),        # What if go_version has no minor?
    ("1.20", "2"),       # What if go_latest has no minor?
]

for go_version, go_latest in test_cases:
    try:
        parts_v = list(map(int, go_version.split('.')[:2]))
        parts_l = list(map(int, go_latest.split('.')[:2]))
        # Pad with 0 if needed
        while len(parts_v) < 2:
            parts_v.append(0)
        while len(parts_l) < 2:
            parts_l.append(0)
        suggested = tuple(parts_v) > tuple(parts_l)
        print(f"{go_version} vs {go_latest}: parts_v={parts_v}, parts_l={parts_l}, suggested={suggested}")
    except (ValueError, IndexError) as e:
        print(f"{go_version} vs {go_latest}: ERROR - {e}")
PY

Repository: openshift-eng/art-tools

Length of output: 184


Fix version comparison logic to use numeric tuple comparison.

The current string-split comparison at line 585 fails to detect legitimate major-version Go upgrades. For example, 1.10.1 vs 1.9 and 2.0.0 vs 1.25 both incorrectly return False. When the condition is False, the replacements map at lines 596-598 is never populated, causing the GO_PREVIOUS/GO_LATEST remap to be skipped for both the main branch and layered branches.

Use numeric tuple comparison to fix this:

Suggested fix
-        elif go_version.split('.')[0] >= go_latest.split('.')[0] and go_version.split('.')[1] > go_latest.split('.')[1]:
+        elif tuple(map(int, go_version.split('.')[:2])) > tuple(map(int, go_latest.split('.')[:2])):
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pyartcd/pyartcd/pipelines/update_golang.py` around lines 596 - 598, The
version comparison currently using string-split is incorrect; change the logic
that checks whether latest_go > previous_go to parse both latest_go and
previous_go into numeric tuples (e.g., split on '.' and map to ints, padding
shorter tuples with zeros) and compare those tuples instead, then ensure that
when the numeric comparison is True you populate the replacements dict with
replacements[latest_go] = new_latest_go and replacements[previous_go] =
latest_go so the GO_PREVIOUS/GO_LATEST remap is applied for the main and layered
branches; update the code paths that reference latest_go, previous_go,
replacements, and any helper currently performing the string comparison to use
the numeric-tuple comparison.

Comment on lines +702 to +719
results = await asyncio.gather(
*[self._update_layered_branch(b, go_version, replacements) for b in sync_branches],
return_exceptions=True,
)

summary_lines = []
for branch_name, result in zip(sync_branches, results):
if isinstance(result, Exception):
summary_lines.append(f" - {branch_name}: :x: error: {result}")
_LOGGER.error("Failed to update layered branch %s: %s", branch_name, result)
elif result is None:
summary_lines.append(f" - {branch_name}: skipped (no matching builders)")
else:
summary_lines.append(f" - {branch_name}: {result}")

summary = f"Layered branch PRs for golang {go_version}:\n" + "\n".join(summary_lines)
_LOGGER.info(summary)
await self._slack_client.say_in_thread(summary)
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Apr 23, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't mark the pipeline successful when layered sync failed.

Exceptions are summarized and then discarded here. That lets run() continue and later post the final success message even when one or more layered-branch PRs were not created.

💡 Suggested fix
-        summary_lines = []
+        summary_lines = []
+        errors = []
         for branch_name, result in zip(sync_branches, results):
             if isinstance(result, Exception):
+                errors.append(f"{branch_name}: {result}")
                 summary_lines.append(f"  - {branch_name}: :x: error: {result}")
                 _LOGGER.error("Failed to update layered branch %s: %s", branch_name, result)
             elif result is None:
                 summary_lines.append(f"  - {branch_name}: skipped (no matching builders)")
             else:
                 summary_lines.append(f"  - {branch_name}: {result}")
 
         summary = f"Layered branch PRs for golang {go_version}:\n" + "\n".join(summary_lines)
         _LOGGER.info(summary)
         await self._slack_client.say_in_thread(summary)
+        if errors:
+            raise RuntimeError("Failed to update layered branches:\n" + "\n".join(errors))
🧰 Tools
🪛 Ruff (0.15.10)

[warning] 708-708: zip() without an explicit strict= parameter

Add explicit value for parameter strict=

(B905)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pyartcd/pyartcd/pipelines/update_golang.py` around lines 702 - 719, The code
currently collects exceptions from asyncio.gather for _update_layered_branch but
only logs and summarizes them, allowing run() to continue as a success; change
this by detecting any Exception in results after the loop and raising an error
(e.g., raise RuntimeError or the first exception) with the built summary so the
pipeline fails properly. Concretely: after building summary (the summary
variable and _LOGGER.info call), if any item in results is an Exception, log the
summary, post it via self._slack_client.say_in_thread, then raise a
RuntimeError(f"Layered branch updates failed: {summary}") (or re-raise the first
Exception) so run() observes failure instead of proceeding. Ensure you reference
_update_layered_branch, results, summary and self._slack_client.say_in_thread in
the change.

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.

@coderabbitai autofix

Signed-off-by: Rayford Johnson <rayfordj@users.noreply.github.com>

rh-pre-commit.version: 2.3.2
rh-pre-commit.check-secrets: ENABLED
@rayfordj
Copy link
Copy Markdown
Contributor Author

@coderabbitai help

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 23, 2026

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

CodeRabbit commands

These commands are invoked using PR/Issue comments.

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai evaluate custom pre-merge check --instructions <custom-checks-instructions> --name <custom-checks-title> [--mode <error|warning>] to validate the custom pre-merge checks instructions. Defaults to error when --mode is omitted.
  • @coderabbitai ignore pre-merge checks to override pre-merge checks and get an approval on PR.
  • @coderabbitai run pre-merge checks to run pre-merge checks on the pull request.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve merge conflict to automatically resolve merge conflicts.
  • @coderabbitai autofix to automatically fix issues identified in unresolved review comments.
  • @coderabbitai resolve to resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai generate configuration to create a PR that adds the current resolved configuration as .coderabbit.yaml (or show it if already present).
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit configuration file (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, support, documentation and community

  • Visit our status page to check the current availability of CodeRabbit.
  • Create a ticket on our support page for assistance with any issues or questions.
  • Visit our documentation site for detailed information on how to use CodeRabbit.
  • Join our Discord community to connect with other users and get help from the community.
  • Follow us on X/Twitter for updates and announcements.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 23, 2026

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

❌ Failed to clone repository into sandbox. Please try again.

@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci Bot commented Apr 30, 2026

PR needs rebase.

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.

@openshift-ci openshift-ci Bot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Apr 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. tide/merge-method-squash Denotes a PR that should be squashed by tide when it merges.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants