Skip to content

enable in-repo ci-op config#5212

Draft
Prucek wants to merge 1 commit into
openshift:mainfrom
Prucek:in-repo-config-plugin
Draft

enable in-repo ci-op config#5212
Prucek wants to merge 1 commit into
openshift:mainfrom
Prucek:in-repo-config-plugin

Conversation

@Prucek

@Prucek Prucek commented May 28, 2026

Copy link
Copy Markdown
Member

Summary

  • Add a Prow external plugin (in-repo-config-plugin) that enables repos to manage CI configuration in-repo via .ci-operator.yaml files instead of the centralized openshift/release repository
  • /onboard command bootstraps a repo with config-checker presubmit and prowgen postsubmit jobs on EFS
  • On PR open/sync, detects new tests in .ci-operator/ configs and creates ephemeral ProwJob definitions + triggers them automatically
  • On first push with .ci-operator/ configs, auto-onboards the repo with bootstrap jobs and initial test definitions
  • Add --from-file mode to ci-operator-prowgen for generating jobs from a single ci-operator config file
  • Add UnresolvedConfig podspec mutator and WriteBranchToDir for branch-scoped job writing

🤖 Generated with Claude Code

Summary

Adds the in-repo-config-plugin to manage OpenShift CI configuration directly from repository .ci-operator/ files.

  • Automatically onboards repositories with config-checker presubmit and prowgen postsubmit jobs.
  • Detects new CI tests on pull requests, creates ephemeral ProwJobs, comments with detected tests, and cleans them up when PRs close.
  • Generates and stores branch-scoped job configuration on pushes to the default branch.
  • Adds ci-operator-prowgen --from-file support for single configuration files.
  • Supports unresolved configuration paths in generated jobs and adds atomic, branch-specific job-config writing.
  • Improves ci-operator-checkconfig behavior when cluster-claim configuration is not provided.
  • Includes unit coverage for bootstrap generation, push/PR handling, path detection, metadata parsing, and unresolved configurations.

@openshift-merge-bot

Copy link
Copy Markdown
Contributor

Pipeline controller notification
This repo is configured to use the pipeline controller. Second-stage tests will be triggered either automatically or after lgtm label is added, depending on the repository configuration. The pipeline controller will automatically detect which contexts are required and will utilize /test Prow commands to trigger the second stage.

For optional jobs, comment /test ? to see a list of all defined jobs. To trigger manually all jobs from second stage use /pipeline required command.

This repository is configured in: automatic mode

@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds an in-repo-config Prow plugin with webhook dispatch, pull request and push handling, bootstrap job generation, persistent branch shards, and ephemeral cleanup. It also extends prowgen to read single config files and preserve unresolved config paths in generated jobs.

Changes

In-repo configuration workflow

Layer / File(s) Summary
Single-file generation and job propagation
cmd/ci-operator-prowgen/main.go, pkg/api/types.go, pkg/jobconfig/files.go, pkg/prowgen/...
Adds --from-file, unresolved-config propagation, branch-scoped atomic job writing, and generated-job fixture coverage.
Bootstrap job definitions
cmd/in-repo-config-plugin/bootstrap.go, cmd/in-repo-config-plugin/bootstrap_test.go
Adds exact-branch config-check presubmits and prowgen postsubmits with path filters, command wiring, decoration, PVC storage, and tests.
Plugin lifecycle and event handling
cmd/in-repo-config-plugin/main.go, cmd/in-repo-config-plugin/server.go, images/in-repo-config-plugin/Dockerfile
Adds bounded webhook dispatch, startup and shutdown wiring, GitHub/Kubernetes integration, PR and push processing, job persistence, comments, and ephemeral cleanup.
Behavior validation
cmd/in-repo-config-plugin/server_test.go, cmd/ci-operator-checkconfig/main.go
Adds fake GitHub infrastructure and tests for PR/push behavior, path matching, metadata parsing, existing jobs, and conditional claim-owner loading.

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

Suggested reviewers: droslean, bear-redhat

🚥 Pre-merge checks | ✅ 14 | ❌ 3

❌ Failed checks (3 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.44% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Go Error Handling ⚠️ Warning FAIL: fetchConfigs swallows any GetDirectory error and falls back to .ci-operator.yaml; auth/rate-limit failures aren’t returned or wrapped. Only fall back on github.FileNotFound; return other GetDirectory errors with fmt.Errorf(%w) and context.
Test Coverage For New Features ⚠️ Warning New helpers in main.go are untested, and pkg/jobconfig has no direct tests for WriteToFileAtomic/WriteBranchToDir. Add table-driven tests for the dispatcher and Validate/gatherOptions paths, plus filesystem tests for WriteToFileAtomic and WriteBranchToDir.
✅ Passed checks (14 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and clearly matches the main change: enabling in-repo CI operator config support.
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.
Stable And Deterministic Test Names ✅ Passed The changed tests use only static, hard-coded t.Run names; no Ginkgo titles or dynamic identifiers/timestamps/UUIDs appear.
Test Structure And Quality ✅ Passed No Ginkgo tests were added; the new table-driven tests use t.TempDir/helpers, no waits, and mostly explicit failure messages consistent with repo style.
Microshift Test Compatibility ✅ Passed No new Ginkgo e2e tests were added; the changed tests are plain Go unit tests with no MicroShift-specific APIs or skip tags.
Single Node Openshift (Sno) Test Compatibility ✅ Passed No new Ginkgo e2e tests were added; the changed tests are plain unit tests and contain no multi-node or SNO-specific assumptions.
Topology-Aware Scheduling Compatibility ✅ Passed PASS: Touched files only add Prow job configs and CLI plumbing; I found no nodeSelector, affinity, topologySpreadConstraints, replicas, PDBs, or control-plane/arbiter scheduling rules.
Ote Binary Stdout Contract ✅ Passed No process-level stdout writes found in main/init paths; logging uses logrus and the fmt.Fprintf calls write to buffers, not stdout.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed No new Ginkgo e2e tests were added; the changed tests are plain Go unit tests, and quay.io appears only as image literals, not network calls.
No-Weak-Crypto ✅ Passed No weak crypto primitives or custom secret comparisons were added; changed files only use Prow’s secret/HMAC helpers and no crypto imports appeared.
Container-Privileges ✅ Passed No new manifest sets privileged, host* namespaces, SYS_ADMIN, allowPrivilegeEscalation, or root securityContext fields; the added Dockerfile has no such config.
No-Sensitive-Data-In-Logs ✅ Passed No new logging emits secret contents or PII; logs are limited to repo metadata, paths, SHAs, and error messages.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@Prucek
Prucek marked this pull request as draft May 28, 2026 13:31
@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 May 28, 2026
@Prucek Prucek changed the title feat(prowgen): add in-repo-config Prow plugin prowgen: add in-repo-config Prow plugin May 28, 2026
@openshift-ci
openshift-ci Bot requested review from bear-redhat and droslean May 28, 2026 13:31
@openshift-ci

openshift-ci Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: Prucek

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

The pull request process is described 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

@openshift-ci openshift-ci Bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label May 28, 2026
@Prucek
Prucek force-pushed the in-repo-config-plugin branch from d862c2b to 2e3de1a Compare May 28, 2026 13:37
@Prucek Prucek changed the title prowgen: add in-repo-config Prow plugin prowgen: enable in-repo ci-op config May 28, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (2)
images/in-repo-config-plugin/Dockerfile (1)

1-3: ⚡ Quick win

Add HEALTHCHECK for operational monitoring.

The Dockerfile lacks a HEALTHCHECK directive. Since this is a webhook server, adding a health endpoint check improves container orchestration and monitoring.

As per coding guidelines: "HEALTHCHECK defined".

💚 Proposed addition
 FROM quay.io/centos/centos:stream8
 ADD in-repo-config-plugin /usr/bin/in-repo-config-plugin
+HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
+  CMD ["/usr/bin/in-repo-config-plugin", "--health-check"]
 ENTRYPOINT ["/usr/bin/in-repo-config-plugin"]

Note: Verify whether the binary supports a --health-check flag or similar. If the webhook server exposes an HTTP health endpoint (common in Prow plugins), use a curl-based check instead:

HEALTHCHECK --interval=30s --timeout=3s CMD curl -f http://localhost:8888/healthz || exit 1
🤖 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 `@images/in-repo-config-plugin/Dockerfile` around lines 1 - 3, The Dockerfile
for the in-repo-config-plugin container has no HEALTHCHECK; add a HEALTHCHECK
directive so orchestration can monitor the webhook process started by ENTRYPOINT
(binary in-repo-config-plugin). If the binary supports a health flag (e.g.,
--health-check or --health-port) invoke it via the HEALTHCHECK CMD, otherwise
use an HTTP probe such as curling the plugin’s health endpoint (for example
http://localhost:8888/healthz) with sensible --interval and --timeout settings
and a non-zero exit on failure; place this HEALTHCHECK line after the ENTRYPOINT
so the liveness probe validates the actual running process.
cmd/in-repo-config-plugin/bootstrap.go (1)

81-82: ⚡ Quick win

Use canonical in-repo config filename constant

At Line 81, ".ci-operator.yaml" is hardcoded. Reuse cioperatorapi.CIOperatorInrepoConfigFileName to avoid drift between bootstrap and /new-test behavior.

Suggested diff
+	cioperatorapi "github.com/openshift/ci-tools/pkg/api"
...
-						fmt.Sprintf("--from-file=%s/.ci-operator.yaml", repoPath),
+						fmt.Sprintf("--from-file=%s/%s", repoPath, cioperatorapi.CIOperatorInrepoConfigFileName),
🤖 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 `@cmd/in-repo-config-plugin/bootstrap.go` around lines 81 - 82, The hardcoded
filename ".ci-operator.yaml" should be replaced with the canonical constant to
avoid drift; update the fmt.Sprintf call that builds the from-file path (the
expression currently using fmt.Sprintf("--from-file=%s/.ci-operator.yaml",
repoPath)) to use cioperatorapi.CIOperatorInrepoConfigFileName instead (e.g.,
fmt.Sprintf("--from-file=%s/%s", repoPath,
cioperatorapi.CIOperatorInrepoConfigFileName)), ensuring you import the
cioperatorapi package if not already imported.
🤖 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 `@cmd/in-repo-config-plugin/server_test.go`:
- Around line 324-415: Add a regression test case in
TestHandleIssueCommentDispatch to assert the `/new-testfoo` edge-case is
ignored: inside the testCases slice add an entry with name like "ignores
/new-testfoo suffix", body "/new-testfoo", action
github.IssueCommentActionCreated, isPR true, and expectComments 0 so that
handleIssueComment (and the fakeGithubClient used in the test) will fail the
test if the `/new-test` matcher wrongly triggers on `/new-testfoo`.

In `@cmd/in-repo-config-plugin/server.go`:
- Around line 122-123: The CreateComment calls on s.ghc
(s.ghc.CreateComment(...)) are ignoring returned errors; update each call
(including the other occurrences you flagged) to capture the error return and
log it (e.g., err := s.ghc.CreateComment(...); if err != nil {
s.logger.Errorf("CreateComment failed for %s/%s#%d: %v", org, repo, number, err)
}) or create a small helper like createCommentAndLog(org, repo, number, body)
that calls s.ghc.CreateComment, checks the error, and logs a descriptive
message; replace the raw calls with that helper to ensure consistent error
handling and logging across server.go.
- Around line 280-289: The success message is built from jobNames (which
includes Presubmits, Postsubmits, and Periodics) but the creation loop only
creates presubmit ProwJobs, causing misleading “ProwJobs created” messages when
nothing was created; update the code that builds the success message to use the
actual created list rather than jobNames (e.g., use the slice/variable you
append created presubmit names to in the creation loop, or filter jobNames to
only the presubmits from allJobs.PresubmitsStatic[orgrepo]) so the message only
reports jobs that were truly created; make the same fix for the corresponding
block covering the other region noted (around the other success message).
- Around line 99-100: The current command match using strings.HasPrefix(body,
newTestPrefix) is too loose and will match inputs like "/new-testfoo"; update
the match in the switch/case around handleNewTest (referencing newTestPrefix and
s.handleNewTest) to only trigger when body == "/new-test" or when body starts
with "/new-test " (slash-new-test followed by a space) so arguments remain
supported but accidental concatenations are rejected; implement the conditional
check accordingly and keep calling s.handleNewTest(l, ic) only when that
stricter match passes.

In `@images/in-repo-config-plugin/Dockerfile`:
- Line 2: Replace the Dockerfile ADD instruction with COPY to avoid ADD's extra
behavior; change the line that currently uses "ADD in-repo-config-plugin
/usr/bin/in-repo-config-plugin" to use "COPY" so the file in-repo-config-plugin
is explicitly copied into /usr/bin/in-repo-config-plugin without unintended
extraction or URL fetching.
- Around line 1-3: The Dockerfile currently runs the plugin as root; add a USER
directive after adding the binary and before ENTRYPOINT to switch to a non-root
UID:GID that matches the EFS and git-sync mounts (e.g., USER 1000:1000 or USER
65532:65532). Update the Dockerfile around the ADD in-repo-config-plugin and
ENTRYPOINT ["/usr/bin/in-repo-config-plugin"] lines to include the chosen USER
and ensure file ownership/permissions on /usr/bin/in-repo-config-plugin and any
expected runtime dirs (like --job-config-dir and --release-repo-dir) are set to
that UID:GID so the container can read/write those mounts.
- Line 1: Replace the non-compliant base image on the Dockerfile's FROM line
(currently "quay.io/centos/centos:stream8") with a UBI minimal or distroless
image sourced from catalog.redhat.com (use the appropriate image name/tag
provided by Red Hat); if you must keep a non-Red Hat base, pin it by digest
(e.g., change "quay.io/centos/centos:stream8" to
"quay.io/centos/centos@sha256:..."). Update the FROM instruction accordingly and
verify the new base meets the project's security rules.

---

Nitpick comments:
In `@cmd/in-repo-config-plugin/bootstrap.go`:
- Around line 81-82: The hardcoded filename ".ci-operator.yaml" should be
replaced with the canonical constant to avoid drift; update the fmt.Sprintf call
that builds the from-file path (the expression currently using
fmt.Sprintf("--from-file=%s/.ci-operator.yaml", repoPath)) to use
cioperatorapi.CIOperatorInrepoConfigFileName instead (e.g.,
fmt.Sprintf("--from-file=%s/%s", repoPath,
cioperatorapi.CIOperatorInrepoConfigFileName)), ensuring you import the
cioperatorapi package if not already imported.

In `@images/in-repo-config-plugin/Dockerfile`:
- Around line 1-3: The Dockerfile for the in-repo-config-plugin container has no
HEALTHCHECK; add a HEALTHCHECK directive so orchestration can monitor the
webhook process started by ENTRYPOINT (binary in-repo-config-plugin). If the
binary supports a health flag (e.g., --health-check or --health-port) invoke it
via the HEALTHCHECK CMD, otherwise use an HTTP probe such as curling the
plugin’s health endpoint (for example http://localhost:8888/healthz) with
sensible --interval and --timeout settings and a non-zero exit on failure; place
this HEALTHCHECK line after the ENTRYPOINT so the liveness probe validates the
actual running process.
🪄 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 YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: 3792b6e7-6ac0-4af1-a70e-c6449770ca46

📥 Commits

Reviewing files that changed from the base of the PR and between cbe040e and d862c2b.

📒 Files selected for processing (6)
  • cmd/in-repo-config-plugin/bootstrap.go
  • cmd/in-repo-config-plugin/bootstrap_test.go
  • cmd/in-repo-config-plugin/main.go
  • cmd/in-repo-config-plugin/server.go
  • cmd/in-repo-config-plugin/server_test.go
  • images/in-repo-config-plugin/Dockerfile

Comment on lines +324 to +415
func TestHandleIssueCommentDispatch(t *testing.T) {
testCases := []struct {
name string
body string
action github.IssueCommentEventAction
isPR bool
expectComments int
}{
{
name: "dispatches /onboard",
body: "/onboard",
action: github.IssueCommentActionCreated,
isPR: true,
expectComments: 1,
},
{
name: "dispatches /new-test",
body: "/new-test e2e",
action: github.IssueCommentActionCreated,
isPR: true,
expectComments: 1,
},
{
name: "ignores non-created actions",
body: "/onboard",
action: github.IssueCommentActionDeleted,
isPR: true,
expectComments: 0,
},
{
name: "ignores non-PR issues",
body: "/onboard",
action: github.IssueCommentActionCreated,
isPR: false,
expectComments: 0,
},
{
name: "ignores unrelated comments",
body: "LGTM",
action: github.IssueCommentActionCreated,
isPR: true,
expectComments: 0,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
tmpDir := t.TempDir()
ghc := &fakeGithubClient{
prs: map[string]*github.PullRequest{
"org/repo#1": makePR("org", "repo", 1, "main", "abc1234567890"),
},
dirs: map[string][]github.DirectoryContent{},
files: map[string][]byte{},
}

pjc := fakectrlruntimeclient.NewClientBuilder().WithScheme(pjScheme()).Build()

s := &server{
ghc: ghc,
trustedChecker: &fakeTrustedChecker{trusted: true},
pjclient: pjc,
namespace: "test-ns",
jobConfigDir: tmpDir,
releaseRepoDir: tmpDir,
prowgenImage: "img",
checkconfigImage: "img",
}

ic := github.IssueCommentEvent{
Action: tc.action,
Repo: github.Repo{Owner: github.User{Login: "org"}, Name: "repo"},
Issue: github.Issue{
Number: 1,
},
Comment: github.IssueComment{
Body: tc.body,
User: github.User{Login: "testuser"},
},
}
if tc.isPR {
ic.Issue.PullRequest = &struct{}{}
}

s.handleIssueComment(logrus.NewEntry(logrus.StandardLogger()), ic)

if len(ghc.comments) != tc.expectComments {
t.Errorf("expected %d comments, got %d: %+v", tc.expectComments, len(ghc.comments), ghc.comments)
}
})
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add dispatch regression test for /new-test prefix edge case

Please add a case asserting /new-testfoo is ignored. This prevents accidental command triggering after the matcher fix in handleIssueComment.

As per coding guidelines, "New or modified functionality should include test coverage. Bug fixes should include a regression test that fails without the fix."

🤖 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 `@cmd/in-repo-config-plugin/server_test.go` around lines 324 - 415, Add a
regression test case in TestHandleIssueCommentDispatch to assert the
`/new-testfoo` edge-case is ignored: inside the testCases slice add an entry
with name like "ignores /new-testfoo suffix", body "/new-testfoo", action
github.IssueCommentActionCreated, isPR true, and expectComments 0 so that
handleIssueComment (and the fakeGithubClient used in the test) will fail the
test if the `/new-test` matcher wrongly triggers on `/new-testfoo`.

Comment thread cmd/in-repo-config-plugin/server.go Outdated
Comment on lines +99 to +100
case strings.HasPrefix(body, newTestPrefix):
s.handleNewTest(l, ic)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Tighten /new-test command matching

At Line 99, HasPrefix("/new-test") also matches unintended inputs (e.g. /new-testfoo). Gate on exact command or /new-test with args.

Suggested diff
-	case strings.HasPrefix(body, newTestPrefix):
+	case body == newTestPrefix || strings.HasPrefix(body, newTestPrefix+" "):
 		s.handleNewTest(l, ic)
📝 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
case strings.HasPrefix(body, newTestPrefix):
s.handleNewTest(l, ic)
case body == newTestPrefix || strings.HasPrefix(body, newTestPrefix+" "):
s.handleNewTest(l, ic)
🤖 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 `@cmd/in-repo-config-plugin/server.go` around lines 99 - 100, The current
command match using strings.HasPrefix(body, newTestPrefix) is too loose and will
match inputs like "/new-testfoo"; update the match in the switch/case around
handleNewTest (referencing newTestPrefix and s.handleNewTest) to only trigger
when body == "/new-test" or when body starts with "/new-test " (slash-new-test
followed by a space) so arguments remain supported but accidental concatenations
are rejected; implement the conditional check accordingly and keep calling
s.handleNewTest(l, ic) only when that stricter match passes.

Comment thread cmd/in-repo-config-plugin/server.go Outdated
Comment thread cmd/in-repo-config-plugin/server.go Outdated
Comment thread images/in-repo-config-plugin/Dockerfile Outdated
Comment thread images/in-repo-config-plugin/Dockerfile Outdated
Comment on lines +1 to +3
FROM quay.io/centos/centos:stream8
ADD in-repo-config-plugin /usr/bin/in-repo-config-plugin
ENTRYPOINT ["/usr/bin/in-repo-config-plugin"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Add USER directive to run as non-root.

The container runs as root by default, violating the security requirement that containers must run as non-root. The plugin writes to EFS-mounted --job-config-dir and reads from the git-sync'd --release-repo-dir, so the USER directive must align with the UID/GID of those volume mounts.

As per coding guidelines: "USER non-root; never run as root".

🛡️ Proposed fix
 FROM quay.io/centos/centos:stream8
 ADD in-repo-config-plugin /usr/bin/in-repo-config-plugin
+USER 1000:1000
 ENTRYPOINT ["/usr/bin/in-repo-config-plugin"]

Note: Verify the UID/GID (1000:1000 is an example) matches the permissions on the EFS and git-sync volume mounts in the deployment manifest. Common choices are 65532:65532 (nonroot) or a specific service account UID.

📝 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
FROM quay.io/centos/centos:stream8
ADD in-repo-config-plugin /usr/bin/in-repo-config-plugin
ENTRYPOINT ["/usr/bin/in-repo-config-plugin"]
FROM quay.io/centos/centos:stream8
ADD in-repo-config-plugin /usr/bin/in-repo-config-plugin
USER 1000:1000
ENTRYPOINT ["/usr/bin/in-repo-config-plugin"]
🧰 Tools
🪛 Hadolint (2.14.0)

[error] 2-2: Use COPY instead of ADD for files and folders

(DL3020)

🪛 Trivy (0.69.3)

[error] 1-1: Image user should not be 'root'

Specify at least 1 USER command in Dockerfile with non-root user as argument

Rule: DS-0002

Learn more

(IaC/Dockerfile)

🤖 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 `@images/in-repo-config-plugin/Dockerfile` around lines 1 - 3, The Dockerfile
currently runs the plugin as root; add a USER directive after adding the binary
and before ENTRYPOINT to switch to a non-root UID:GID that matches the EFS and
git-sync mounts (e.g., USER 1000:1000 or USER 65532:65532). Update the
Dockerfile around the ADD in-repo-config-plugin and ENTRYPOINT
["/usr/bin/in-repo-config-plugin"] lines to include the chosen USER and ensure
file ownership/permissions on /usr/bin/in-repo-config-plugin and any expected
runtime dirs (like --job-config-dir and --release-repo-dir) are set to that
UID:GID so the container can read/write those mounts.

@@ -0,0 +1,3 @@
FROM quay.io/centos/centos:stream8
ADD in-repo-config-plugin /usr/bin/in-repo-config-plugin

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use COPY instead of ADD for files.

ADD has additional features (auto-extraction, URL fetching) that aren't needed for simple file copying. COPY is more explicit and preferred.

As per coding guidelines: "COPY specific files, not entire context".

📝 Proposed fix
-ADD in-repo-config-plugin /usr/bin/in-repo-config-plugin
+COPY in-repo-config-plugin /usr/bin/in-repo-config-plugin
🧰 Tools
🪛 Hadolint (2.14.0)

[error] 2-2: Use COPY instead of ADD for files and folders

(DL3020)

🤖 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 `@images/in-repo-config-plugin/Dockerfile` at line 2, Replace the Dockerfile
ADD instruction with COPY to avoid ADD's extra behavior; change the line that
currently uses "ADD in-repo-config-plugin /usr/bin/in-repo-config-plugin" to use
"COPY" so the file in-repo-config-plugin is explicitly copied into
/usr/bin/in-repo-config-plugin without unintended extraction or URL fetching.

@Prucek Prucek changed the title prowgen: enable in-repo ci-op config enable in-repo ci-op config May 28, 2026
@Prucek
Prucek force-pushed the in-repo-config-plugin branch from 2e3de1a to a92eaa5 Compare May 28, 2026 13:40

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🤖 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 `@cmd/ci-operator-prowgen/main.go`:
- Around line 135-137: The code currently assigns a host filesystem path
(o.fromFile) directly to configSpec.UnresolvedConfigPath which embeds an invalid
container path in generated jobs; change the assignment to a
repo/container-relative name by stripping host directories (e.g.,
configSpec.UnresolvedConfigPath = filepath.Base(o.fromFile) or otherwise compute
a repo-relative path) and ensure filepath import is added and empty/null cases
handled before calling prowgen.GenerateJobs(&configSpec, &info). This keeps the
generated job path valid inside the job container while still using the provided
file name.

In `@cmd/in-repo-config-plugin/server.go`:
- Around line 417-423: Change dirExists to return (bool, error) instead of
swallowing all stat errors: call os.Stat(path), if err == nil return
info.IsDir(), nil; if os.IsNotExist(err) return false, nil; otherwise return
false and propagate/wrap the error (e.g. fmt.Errorf("stat %s: %w", path, err)).
Update any callers of dirExists to handle the error path instead of assuming
false means "missing". This preserves permission/I/O errors while keeping
missing-file semantics.
- Around line 220-223: The loop that sets configSpec.UnresolvedConfigPath to the
constant cioperatorapi.CIOperatorInrepoConfigFileName causes every generated job
to point to ".ci-operator.yaml"; instead, carry the actual source path from
fetchConfigs into each configSpec (preserve the original filename or source path
returned by fetchConfigs), and set configSpec.UnresolvedConfigPath = <that
source path> inside the for filename, configSpec := range configs loop before
calling prowgen.GenerateJobs(info) so only the single-file fallback uses the
constant; update fetchConfigs to return the source path per config if it doesn't
already and use that value here (references: fetchConfigs, metadataFromFilename,
configSpec.UnresolvedConfigPath, prowgen.GenerateJobs).
- Around line 389-393: The commentError function currently posts the raw err
into a GitHub comment; change it to post a sanitized, generic user-facing
message instead (e.g., "@%s: command `%s` failed, please check plugin logs or
contact maintainers"), and send the full error details to the logger only.
Concretely, update commentError (and the call to s.ghc.CreateComment) to format
and post a redacted string without err, and call
l.WithError(err).Error("detailed error handling message") so the full error
stays in logrus; keep s.ghc.CreateComment and l references unchanged.

In `@pkg/jobconfig/files.go`:
- Around line 422-427: The current WriteBranchToDir path (loop over files ->
sortConfigFields(jc) -> WriteToFile(filepath.Join(jobDirForComponent, file),
jc)) blindly overwrites per-branch files and thus drops manually-managed fields;
update WriteBranchToDir to read the existing file (if present) for each target
path, merge/retain fields not managed by prowgen into the new jc (preserving
reporter_config and other unmanaged keys) before calling WriteToFile, using the
same file key from the files map and the jobDirForComponent path to locate the
existing content; implement the merge logic in a helper (e.g.,
mergePreservingUnmanaged) and call it prior to sortConfigFields/WriteToFile so
unmanaged fields remain intact.

In `@pkg/prowgen/jobbase.go`:
- Around line 42-44: The code is inserting only the basename of
UnresolvedConfigPath which truncates nested paths and causes sparse-checkout to
drop directories; update the insertion to use the full UnresolvedConfigPath
(i.e., pass configSpec.UnresolvedConfigPath to files.Insert rather than
path.Base(configSpec.UnresolvedConfigPath)) so the exact unresolved config path
is preserved; locate the occurrence around configSpec.UnresolvedConfigPath and
files.Insert to make this change.

In `@pkg/prowgen/podspec.go`:
- Around line 566-572: Add a Go doc comment immediately above the exported
UnresolvedConfig function describing its purpose, parameters, and return value:
explain that UnresolvedConfig(configPath string) returns a PodSpecMutator which
appends a command-line flag "--unresolved-config=<configPath>" to the first
container in a corev1.PodSpec; mention that it mutates the given PodSpec and
returns an error if mutation fails (or nil on success). Keep the comment concise
and follow Go doc style (start with "UnresolvedConfig").
🪄 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 YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: 159822a4-064e-4352-8f22-4ae28f85f17f

📥 Commits

Reviewing files that changed from the base of the PR and between d862c2b and a92eaa5.

📒 Files selected for processing (13)
  • cmd/ci-operator-prowgen/main.go
  • cmd/in-repo-config-plugin/bootstrap.go
  • cmd/in-repo-config-plugin/bootstrap_test.go
  • cmd/in-repo-config-plugin/main.go
  • cmd/in-repo-config-plugin/server.go
  • cmd/in-repo-config-plugin/server_test.go
  • images/in-repo-config-plugin/Dockerfile
  • pkg/api/types.go
  • pkg/jobconfig/files.go
  • pkg/prowgen/jobbase.go
  • pkg/prowgen/jobbase_test.go
  • pkg/prowgen/podspec.go
  • pkg/prowgen/testdata/zz_fixture_TestProwJobBaseBuilder_job_with_unresolved_config__including_podspec.yaml
✅ Files skipped from review due to trivial changes (1)
  • pkg/prowgen/testdata/zz_fixture_TestProwJobBaseBuilder_job_with_unresolved_config__including_podspec.yaml
🚧 Files skipped from review as they are similar to previous changes (4)
  • cmd/in-repo-config-plugin/bootstrap.go
  • cmd/in-repo-config-plugin/bootstrap_test.go
  • cmd/in-repo-config-plugin/server_test.go
  • cmd/in-repo-config-plugin/main.go

Comment on lines +135 to +137
configSpec.UnresolvedConfigPath = o.fromFile
generated, err := prowgen.GenerateJobs(&configSpec, &info)
if err != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

--unresolved-config is populated with a host path, not a repo path.

On Line 135, UnresolvedConfigPath is set to o.fromFile directly. In --from-file usage this can be an absolute/local path, which is then embedded in generated jobs and won’t exist inside the job container.

Suggested fix
-	configSpec.UnresolvedConfigPath = o.fromFile
+	// ci-operator runs in the repo checkout; unresolved config path must be repo-relative.
+	configSpec.UnresolvedConfigPath = filepath.Base(o.fromFile)
📝 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
configSpec.UnresolvedConfigPath = o.fromFile
generated, err := prowgen.GenerateJobs(&configSpec, &info)
if err != nil {
// ci-operator runs in the repo checkout; unresolved config path must be repo-relative.
configSpec.UnresolvedConfigPath = filepath.Base(o.fromFile)
generated, err := prowgen.GenerateJobs(&configSpec, &info)
if err != nil {
🤖 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 `@cmd/ci-operator-prowgen/main.go` around lines 135 - 137, The code currently
assigns a host filesystem path (o.fromFile) directly to
configSpec.UnresolvedConfigPath which embeds an invalid container path in
generated jobs; change the assignment to a repo/container-relative name by
stripping host directories (e.g., configSpec.UnresolvedConfigPath =
filepath.Base(o.fromFile) or otherwise compute a repo-relative path) and ensure
filepath import is added and empty/null cases handled before calling
prowgen.GenerateJobs(&configSpec, &info). This keeps the generated job path
valid inside the job container while still using the provided file name.

Comment on lines +220 to +223
for filename, configSpec := range configs {
info := metadataFromFilename(filename, org, repo, branch)
configSpec.UnresolvedConfigPath = cioperatorapi.CIOperatorInrepoConfigFileName
generated, err := prowgen.GenerateJobs(configSpec, info)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use the source config path here.

Every generated job gets UnresolvedConfigPath = ".ci-operator.yaml", even when it came from .ci-operator/<file>.yaml. Those ProwJobs will point ci-operator at the wrong file and fail on repos that only use split configs. Carry the actual source path through fetchConfigs and set it per config; only the single-file fallback should use .ci-operator.yaml.

🤖 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 `@cmd/in-repo-config-plugin/server.go` around lines 220 - 223, The loop that
sets configSpec.UnresolvedConfigPath to the constant
cioperatorapi.CIOperatorInrepoConfigFileName causes every generated job to point
to ".ci-operator.yaml"; instead, carry the actual source path from fetchConfigs
into each configSpec (preserve the original filename or source path returned by
fetchConfigs), and set configSpec.UnresolvedConfigPath = <that source path>
inside the for filename, configSpec := range configs loop before calling
prowgen.GenerateJobs(info) so only the single-file fallback uses the constant;
update fetchConfigs to return the source path per config if it doesn't already
and use that value here (references: fetchConfigs, metadataFromFilename,
configSpec.UnresolvedConfigPath, prowgen.GenerateJobs).

Comment thread cmd/in-repo-config-plugin/server.go Outdated
Comment on lines +417 to +423
func dirExists(path string) bool {
info, err := os.Stat(path)
if err != nil {
return false
}
return info.IsDir()
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Distinguish "missing" from "stat failed".

dirExists collapses permission errors, broken mounts, and transient I/O failures into false. In this plugin that means onboarding and /new-test can proceed as if no jobs exist when EFS or the release repo is actually unhealthy. Return (bool, error) and only treat os.IsNotExist as absence. As per coding guidelines, "Errors should be handled correctly - determine whether to ignore, log, wrap and raise up; use informative error messages".

🤖 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 `@cmd/in-repo-config-plugin/server.go` around lines 417 - 423, Change dirExists
to return (bool, error) instead of swallowing all stat errors: call
os.Stat(path), if err == nil return info.IsDir(), nil; if os.IsNotExist(err)
return false, nil; otherwise return false and propagate/wrap the error (e.g.
fmt.Errorf("stat %s: %w", path, err)). Update any callers of dirExists to handle
the error path instead of assuming false means "missing". This preserves
permission/I/O errors while keeping missing-file semantics.

Comment thread pkg/jobconfig/files.go
Comment on lines +422 to +427
for file, jc := range files {
sortConfigFields(jc)
if err := WriteToFile(filepath.Join(jobDirForComponent, file), jc); err != nil {
return err
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

WriteBranchToDir overwrites branch files without preserving unmanaged fields.

The new write path replaces existing per-branch files directly, so manually maintained fields in those files can be lost (for example reporter config or cluster/max concurrency adjustments).

Suggested direction
-	for file, jc := range files {
-		sortConfigFields(jc)
-		if err := WriteToFile(filepath.Join(jobDirForComponent, file), jc); err != nil {
-			return err
-		}
-	}
+	for file, generated := range files {
+		target := filepath.Join(jobDirForComponent, file)
+		existing, err := readFromFile(target)
+		if err != nil && !os.IsNotExist(err) {
+			return err
+		}
+		if existing != nil {
+			allJobs := sets.New[string]()
+			for _, jobs := range generated.PresubmitsStatic {
+				for _, j := range jobs {
+					allJobs.Insert(j.Name)
+				}
+			}
+			for _, jobs := range generated.PostsubmitsStatic {
+				for _, j := range jobs {
+					allJobs.Insert(j.Name)
+				}
+			}
+			for _, j := range generated.Periodics {
+				allJobs.Insert(j.Name)
+			}
+			mergeJobConfig(existing, generated, allJobs)
+			generated = existing
+		}
+		sortConfigFields(generated)
+		if err := WriteToFile(target, generated); err != nil {
+			return err
+		}
+	}

As per coding guidelines, "pkg/jobconfig/**: ... Must preserve fields that are not managed by prowgen (e.g. manually-set reporter_config)."

📝 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
for file, jc := range files {
sortConfigFields(jc)
if err := WriteToFile(filepath.Join(jobDirForComponent, file), jc); err != nil {
return err
}
}
for file, generated := range files {
target := filepath.Join(jobDirForComponent, file)
existing, err := readFromFile(target)
if err != nil && !os.IsNotExist(err) {
return err
}
if existing != nil {
allJobs := sets.New[string]()
for _, jobs := range generated.PresubmitsStatic {
for _, j := range jobs {
allJobs.Insert(j.Name)
}
}
for _, jobs := range generated.PostsubmitsStatic {
for _, j := range jobs {
allJobs.Insert(j.Name)
}
}
for _, j := range generated.Periodics {
allJobs.Insert(j.Name)
}
mergeJobConfig(existing, generated, allJobs)
generated = existing
}
sortConfigFields(generated)
if err := WriteToFile(target, generated); err != nil {
return err
}
}
🤖 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 `@pkg/jobconfig/files.go` around lines 422 - 427, The current WriteBranchToDir
path (loop over files -> sortConfigFields(jc) ->
WriteToFile(filepath.Join(jobDirForComponent, file), jc)) blindly overwrites
per-branch files and thus drops manually-managed fields; update WriteBranchToDir
to read the existing file (if present) for each target path, merge/retain fields
not managed by prowgen into the new jc (preserving reporter_config and other
unmanaged keys) before calling WriteToFile, using the same file key from the
files map and the jobDirForComponent path to locate the existing content;
implement the merge logic in a helper (e.g., mergePreservingUnmanaged) and call
it prior to sortConfigFields/WriteToFile so unmanaged fields remain intact.

Comment thread pkg/prowgen/jobbase.go
Comment on lines +42 to +44
if configSpec.UnresolvedConfigPath != "" {
files.Insert(path.Base(configSpec.UnresolvedConfigPath))
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Sparse-checkout drops unresolved config directories.

Line 43 uses path.Base, so nested unresolved config paths are truncated. That can fetch the wrong file (or none) while --unresolved-config still references the full path.

Suggested fix
-	if configSpec.UnresolvedConfigPath != "" {
-		files.Insert(path.Base(configSpec.UnresolvedConfigPath))
-	}
+	if configSpec.UnresolvedConfigPath != "" {
+		files.Insert(path.Clean(configSpec.UnresolvedConfigPath))
+	}
📝 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
if configSpec.UnresolvedConfigPath != "" {
files.Insert(path.Base(configSpec.UnresolvedConfigPath))
}
if configSpec.UnresolvedConfigPath != "" {
files.Insert(path.Clean(configSpec.UnresolvedConfigPath))
}
🤖 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 `@pkg/prowgen/jobbase.go` around lines 42 - 44, The code is inserting only the
basename of UnresolvedConfigPath which truncates nested paths and causes
sparse-checkout to drop directories; update the insertion to use the full
UnresolvedConfigPath (i.e., pass configSpec.UnresolvedConfigPath to files.Insert
rather than path.Base(configSpec.UnresolvedConfigPath)) so the exact unresolved
config path is preserved; locate the occurrence around
configSpec.UnresolvedConfigPath and files.Insert to make this change.

Comment thread pkg/prowgen/podspec.go
Comment on lines +566 to +572
func UnresolvedConfig(configPath string) PodSpecMutator {
return func(spec *corev1.PodSpec) error {
container := &spec.Containers[0]
addUniqueParameter(container, fmt.Sprintf("--unresolved-config=%s", configPath))
return nil
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add a doc comment for exported UnresolvedConfig.

This exported function is missing a Go doc comment, which makes public API behavior less discoverable.

As per coding guidelines, "Go documentation on Classes/Functions/Fields should be written properly" and "Comment important exported functions with their purpose, parameters, and return values."

🤖 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 `@pkg/prowgen/podspec.go` around lines 566 - 572, Add a Go doc comment
immediately above the exported UnresolvedConfig function describing its purpose,
parameters, and return value: explain that UnresolvedConfig(configPath string)
returns a PodSpecMutator which appends a command-line flag
"--unresolved-config=<configPath>" to the first container in a corev1.PodSpec;
mention that it mutates the given PodSpec and returns an error if mutation fails
(or nil on success). Keep the comment concise and follow Go doc style (start
with "UnresolvedConfig").

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
cmd/in-repo-config-plugin/server.go (1)

590-649: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

GC mutates ephemeral dirs without the repo lock.

gcEphemeralDirs calls os.RemoveAll on a PR directory without acquiring repoLock(org, repo), while handlePROpenedOrUpdated/handlePRClosed write and delete the same paths under that lock. A close-then-reopen (or sync racing GC) can delete a directory mid-write. Take the repo lock before removing.

🤖 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 `@cmd/in-repo-config-plugin/server.go` around lines 590 - 649, gcEphemeralDirs
removes PR directories without synchronizing with the per-repo lock, which can
race with handlePROpenedOrUpdated and handlePRClosed. Update gcEphemeralDirs to
acquire the same repoLock(org, repo) before calling os.RemoveAll on a PR path,
and hold it only around the delete/check for that repo. Keep the existing
org/repo/PR traversal and logging, but ensure cleanup uses the same locking
discipline as the write/delete paths.

Source: Coding guidelines

🤖 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 `@cmd/in-repo-config-plugin/server.go`:
- Around line 268-298: Handle the prowconfig.Load failure in server.go by
stopping the trigger flow instead of continuing with a nil prowCfg. In the
ProwJob defaulting logic inside the presubmit creation path, return immediately
after logging the load error so jobs are not created without namespace or
DecorationConfig defaults. Use the existing prowconfig.Load call, prowCfg
variable, and the surrounding job-processing loop to locate the fix.

---

Nitpick comments:
In `@cmd/in-repo-config-plugin/server.go`:
- Around line 590-649: gcEphemeralDirs removes PR directories without
synchronizing with the per-repo lock, which can race with
handlePROpenedOrUpdated and handlePRClosed. Update gcEphemeralDirs to acquire
the same repoLock(org, repo) before calling os.RemoveAll on a PR path, and hold
it only around the delete/check for that repo. Keep the existing org/repo/PR
traversal and logging, but ensure cleanup uses the same locking discipline as
the write/delete paths.
🪄 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 YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: bb62c45e-bbe3-4966-8875-2786f7c95ffb

📥 Commits

Reviewing files that changed from the base of the PR and between a92eaa5 and c32c4e8.

📒 Files selected for processing (13)
  • cmd/ci-operator-prowgen/main.go
  • cmd/in-repo-config-plugin/bootstrap.go
  • cmd/in-repo-config-plugin/bootstrap_test.go
  • cmd/in-repo-config-plugin/main.go
  • cmd/in-repo-config-plugin/server.go
  • cmd/in-repo-config-plugin/server_test.go
  • images/in-repo-config-plugin/Dockerfile
  • pkg/api/types.go
  • pkg/jobconfig/files.go
  • pkg/prowgen/jobbase.go
  • pkg/prowgen/jobbase_test.go
  • pkg/prowgen/podspec.go
  • pkg/prowgen/testdata/zz_fixture_TestProwJobBaseBuilder_job_with_unresolved_config__including_podspec.yaml
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • openshift/release (manual)
  • openshift/ci-docs (manual)
  • openshift/release-controller (manual)
  • openshift/ci-chat-bot (manual)
✅ Files skipped from review due to trivial changes (1)
  • pkg/prowgen/testdata/zz_fixture_TestProwJobBaseBuilder_job_with_unresolved_config__including_podspec.yaml
🚧 Files skipped from review as they are similar to previous changes (8)
  • pkg/api/types.go
  • cmd/in-repo-config-plugin/bootstrap_test.go
  • pkg/prowgen/podspec.go
  • pkg/prowgen/jobbase.go
  • pkg/jobconfig/files.go
  • cmd/in-repo-config-plugin/bootstrap.go
  • cmd/ci-operator-prowgen/main.go
  • pkg/prowgen/jobbase_test.go

Comment thread cmd/in-repo-config-plugin/server.go
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Prucek
Prucek force-pushed the in-repo-config-plugin branch from c32c4e8 to 116917d Compare July 15, 2026 13:44

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (6)
images/in-repo-config-plugin/Dockerfile (2)

2-2: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use COPY instead of ADD.

ADD's extra behavior (extraction, URL fetch) isn't needed for a plain binary copy; Hadolint DL3020 flags this too.

As per coding guidelines, "COPY specific files, not entire context".

📝 Proposed fix
-ADD in-repo-config-plugin /usr/bin/in-repo-config-plugin
+COPY in-repo-config-plugin /usr/bin/in-repo-config-plugin
🤖 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 `@images/in-repo-config-plugin/Dockerfile` at line 2, Replace the Dockerfile’s
ADD instruction for in-repo-config-plugin with COPY, targeting only the required
binary rather than the entire build context, while preserving the
/usr/bin/in-repo-config-plugin destination.

Source: Coding guidelines


1-3: 🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win

Still runs as root.

No USER directive is present; the container runs as root by default, which Trivy also flags (DS-0002). This plugin writes to EFS-mounted --job-config-dir, so the UID/GID must align with those volume-mount permissions.

As per coding guidelines, "USER non-root; never run as root".

🛡️ Proposed fix
 FROM registry.access.redhat.com/ubi9/ubi-minimal:latest
 ADD in-repo-config-plugin /usr/bin/in-repo-config-plugin
+USER 1000:1000
 ENTRYPOINT ["/usr/bin/in-repo-config-plugin"]

Confirm the UID/GID matches the EFS/git-sync mount permissions used in the deployment manifest.

🤖 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 `@images/in-repo-config-plugin/Dockerfile` around lines 1 - 3, Add a non-root
USER directive to the in-repo-config-plugin image, using a UID/GID confirmed to
match the EFS/git-sync volume permissions in the deployment manifest, and place
it before ENTRYPOINT so the plugin never runs as root.

Source: Coding guidelines

pkg/prowgen/jobbase.go (1)

42-44: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Still truncating nested unresolved-config paths.

path.Base(configSpec.UnresolvedConfigPath) drops any directory components, so sparse-checkout can fetch the wrong file (or none) for nested UnresolvedConfigPath values, while the actual --unresolved-config flag added in NewProwJobBaseBuilder still references the full path.

🐛 Proposed fix
-	if configSpec.UnresolvedConfigPath != "" {
-		files.Insert(path.Base(configSpec.UnresolvedConfigPath))
-	}
+	if configSpec.UnresolvedConfigPath != "" {
+		files.Insert(path.Clean(configSpec.UnresolvedConfigPath))
+	}
🤖 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 `@pkg/prowgen/jobbase.go` around lines 42 - 44, Update the unresolved-config
insertion logic to preserve the complete configSpec.UnresolvedConfigPath rather
than applying path.Base, so sparse-checkout includes nested paths consistently
with the --unresolved-config value configured by NewProwJobBaseBuilder.
cmd/in-repo-config-plugin/server.go (2)

294-297: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Wrong UnresolvedConfigPath for split configs, at two sites. Every generated job gets UnresolvedConfigPath = ".ci-operator.yaml" regardless of whether the source was the single-file fallback or a file under .ci-operator/; for split-config repos the resulting ProwJobs will point ci-operator at the wrong file and fail. Flagged previously and still present in both handlers.

  • cmd/in-repo-config-plugin/server.go#L294-L297: in handlePush, set configSpec.UnresolvedConfigPath from the actual source path per filename (e.g. filepath.Join(ciOperatorDir, filename) for directory-listing results), reserving the constant only for the fetchSingleConfig fallback.
  • cmd/in-repo-config-plugin/server.go#L105-L108: apply the identical fix in handlePROpenedOrUpdated, since it loops over the same configs map with the same bug.
🤖 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 `@cmd/in-repo-config-plugin/server.go` around lines 294 - 297, Update
UnresolvedConfigPath assignment in handlePush at
cmd/in-repo-config-plugin/server.go lines 294-297 and handlePROpenedOrUpdated at
lines 105-108 to use the actual source path for each split config, such as
filepath.Join(ciOperatorDir, filename); retain CIOperatorInrepoConfigFileName
only for configs returned by fetchSingleConfig.

481-487: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

dirExists still collapses all stat failures to "missing".

Permission errors, broken EFS mounts, and transient I/O failures are indistinguishable from "doesn't exist" here. filterNewJobs (Line 346) and gcEphemeralDirs (Line 505) both rely on this, so an unhealthy mount would make onboarding/detection silently behave as if no jobs/ephemeral dirs exist. Same concern flagged on a prior commit, still unaddressed.

As per coding guidelines, "Errors should be handled correctly - determine whether to ignore, log, wrap and raise up; use informative error messages."

🛡️ Proposed fix
-func dirExists(path string) bool {
-	info, err := os.Stat(path)
-	if err != nil {
-		return false
-	}
-	return info.IsDir()
-}
+func dirExists(path string) (bool, error) {
+	info, err := os.Stat(path)
+	if err == nil {
+		return info.IsDir(), nil
+	}
+	if os.IsNotExist(err) {
+		return false, nil
+	}
+	return false, fmt.Errorf("stat %s: %w", path, err)
+}
🤖 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 `@cmd/in-repo-config-plugin/server.go` around lines 481 - 487, Update dirExists
to distinguish a nonexistent path from other os.Stat failures instead of
returning false for every error. Propagate or otherwise surface permission,
mount, and I/O errors, and update filterNewJobs and gcEphemeralDirs to handle
the resulting error explicitly while preserving false only for paths confirmed
not to exist.

Source: Coding guidelines

pkg/jobconfig/files.go (1)

374-430: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Still overwrites branch files without preserving unmanaged fields.

WriteBranchToDir writes generated shards straight to disk via WriteToFileAtomic with no read-and-merge of existing content, so manually-maintained fields (e.g. reporter_config, cluster/max-concurrency overrides) in those per-branch files are lost on every write. This was already flagged on a prior commit and remains unaddressed.

As per path instructions, "pkg/jobconfig/**: ... Must preserve fields that are not managed by prowgen (e.g. manually-set reporter_config)."

🤖 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 `@pkg/jobconfig/files.go` around lines 374 - 430, The WriteBranchToDir function
must preserve unmanaged fields in existing per-branch files instead of replacing
them. Before each WriteToFileAtomic call, read the corresponding existing shard,
merge its non-prowgen fields into the generated JobConfig, and then write the
merged result; preserve generated jobs and managed-field updates while retaining
fields such as reporter_config and cluster/max-concurrency overrides.

Source: Path instructions

🧹 Nitpick comments (3)
images/in-repo-config-plugin/Dockerfile (1)

1-4: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

No HEALTHCHECK defined.

Container security guidelines for this path call for a defined HEALTHCHECK.

As per coding guidelines, "HEALTHCHECK defined".

🤖 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 `@images/in-repo-config-plugin/Dockerfile` around lines 1 - 4, Add a Docker
HEALTHCHECK instruction to the image defined by the Dockerfile, using the
existing /usr/bin/in-repo-config-plugin entrypoint or an appropriate lightweight
availability check, while preserving the current ADD and ENTRYPOINT behavior.

Source: Coding guidelines

cmd/in-repo-config-plugin/server.go (1)

162-220: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated ProwJob defaulting logic.

The cluster/namespace/DecorationConfig defaulting block is repeated almost verbatim for the presubmit loop and the periodic-as-presubmit loop. Consider extracting a small helper (e.g. applyProwJobDefaults(job *prowconfig.JobBase, prowCfg, orgrepo)) to keep the two loops in sync going forward.

🤖 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 `@cmd/in-repo-config-plugin/server.go` around lines 162 - 220, The presubmit
and periodic-as-presubmit loops duplicate cluster, namespace, and
DecorationConfig defaulting. Extract this logic into a shared helper accepting
the relevant JobBase, prowCfg, and orgrepo values, then call it from both loops
before constructing the ProwJob while preserving the current defaults and
decoration behavior.
cmd/in-repo-config-plugin/server_test.go (1)

115-243: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Solid coverage for PR open/sync/close paths. Once the filterNewJobs postsubmit-drop issue in server.go is fixed, consider adding a case here with only a new postsubmit test to lock in the fix.

🤖 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 `@cmd/in-repo-config-plugin/server_test.go` around lines 115 - 243, The
TestHandlePullRequest table lacks coverage for a PR containing only a newly
added postsubmit test. After fixing filterNewJobs in handlePullRequest’s server
flow, add a test case with only a new postsubmit configuration and assert the
expected job generation/comment behavior, preserving the existing
open/synchronize/close coverage.
🤖 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 `@cmd/in-repo-config-plugin/server.go`:
- Around line 118-127: Acquire the repository lock before calling filterNewJobs
so its jc.ReadFromDir state read is serialized with handlePush’s
WriteBranchToDir writes. Move the repoLock acquisition and deferred unlock ahead
of the filterNewJobs call, while preserving the existing no-new-tests early
return within the locked section.
- Around line 343-389: Update filterNewJobs to collect newly detected postsubmit
jobs in a newPostsubmits slice and return it alongside the existing results.
Thread that slice through handlePROpenedOrUpdated into
ephemeralJobs.PostsubmitsStatic[orgrepo], and include the postsubmit names in
the “New tests detected” comment listing so postsubmit-only changes are
preserved and reported.
- Around line 162-220: Add a bounded timeout context for ProwJob creation and
use it for both s.pjclient.Create calls in the newPresubmits and newPeriodics
loops. Replace each context.Background() passed to Create with the
deadline-bearing context, ensuring stalled API requests cannot block
indefinitely.
- Around line 391-395: Update fetchConfigs so it calls fetchSingleConfig only
when GetDirectory returns github.FileNotFound; return the original GetDirectory
error for authentication, rate-limit, and other failures. Preserve the existing
successful directory-processing path and use the repository’s existing GitHub
error symbols or matching mechanism.

---

Duplicate comments:
In `@cmd/in-repo-config-plugin/server.go`:
- Around line 294-297: Update UnresolvedConfigPath assignment in handlePush at
cmd/in-repo-config-plugin/server.go lines 294-297 and handlePROpenedOrUpdated at
lines 105-108 to use the actual source path for each split config, such as
filepath.Join(ciOperatorDir, filename); retain CIOperatorInrepoConfigFileName
only for configs returned by fetchSingleConfig.
- Around line 481-487: Update dirExists to distinguish a nonexistent path from
other os.Stat failures instead of returning false for every error. Propagate or
otherwise surface permission, mount, and I/O errors, and update filterNewJobs
and gcEphemeralDirs to handle the resulting error explicitly while preserving
false only for paths confirmed not to exist.

In `@images/in-repo-config-plugin/Dockerfile`:
- Line 2: Replace the Dockerfile’s ADD instruction for in-repo-config-plugin
with COPY, targeting only the required binary rather than the entire build
context, while preserving the /usr/bin/in-repo-config-plugin destination.
- Around line 1-3: Add a non-root USER directive to the in-repo-config-plugin
image, using a UID/GID confirmed to match the EFS/git-sync volume permissions in
the deployment manifest, and place it before ENTRYPOINT so the plugin never runs
as root.

In `@pkg/jobconfig/files.go`:
- Around line 374-430: The WriteBranchToDir function must preserve unmanaged
fields in existing per-branch files instead of replacing them. Before each
WriteToFileAtomic call, read the corresponding existing shard, merge its
non-prowgen fields into the generated JobConfig, and then write the merged
result; preserve generated jobs and managed-field updates while retaining fields
such as reporter_config and cluster/max-concurrency overrides.

In `@pkg/prowgen/jobbase.go`:
- Around line 42-44: Update the unresolved-config insertion logic to preserve
the complete configSpec.UnresolvedConfigPath rather than applying path.Base, so
sparse-checkout includes nested paths consistently with the --unresolved-config
value configured by NewProwJobBaseBuilder.

---

Nitpick comments:
In `@cmd/in-repo-config-plugin/server_test.go`:
- Around line 115-243: The TestHandlePullRequest table lacks coverage for a PR
containing only a newly added postsubmit test. After fixing filterNewJobs in
handlePullRequest’s server flow, add a test case with only a new postsubmit
configuration and assert the expected job generation/comment behavior,
preserving the existing open/synchronize/close coverage.

In `@cmd/in-repo-config-plugin/server.go`:
- Around line 162-220: The presubmit and periodic-as-presubmit loops duplicate
cluster, namespace, and DecorationConfig defaulting. Extract this logic into a
shared helper accepting the relevant JobBase, prowCfg, and orgrepo values, then
call it from both loops before constructing the ProwJob while preserving the
current defaults and decoration behavior.

In `@images/in-repo-config-plugin/Dockerfile`:
- Around line 1-4: Add a Docker HEALTHCHECK instruction to the image defined by
the Dockerfile, using the existing /usr/bin/in-repo-config-plugin entrypoint or
an appropriate lightweight availability check, while preserving the current ADD
and ENTRYPOINT behavior.
🪄 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 YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: 6d101c87-91e1-468f-a892-caebbcf9a218

📥 Commits

Reviewing files that changed from the base of the PR and between c32c4e8 and 116917d.

📒 Files selected for processing (14)
  • cmd/ci-operator-checkconfig/main.go
  • cmd/ci-operator-prowgen/main.go
  • cmd/in-repo-config-plugin/bootstrap.go
  • cmd/in-repo-config-plugin/bootstrap_test.go
  • cmd/in-repo-config-plugin/main.go
  • cmd/in-repo-config-plugin/server.go
  • cmd/in-repo-config-plugin/server_test.go
  • images/in-repo-config-plugin/Dockerfile
  • pkg/api/types.go
  • pkg/jobconfig/files.go
  • pkg/prowgen/jobbase.go
  • pkg/prowgen/jobbase_test.go
  • pkg/prowgen/podspec.go
  • pkg/prowgen/testdata/zz_fixture_TestProwJobBaseBuilder_job_with_unresolved_config__including_podspec.yaml
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • openshift/release (manual)
  • openshift/ci-docs (manual)
  • openshift/release-controller (manual)
  • openshift/ci-chat-bot (manual)
🚧 Files skipped from review as they are similar to previous changes (7)
  • pkg/prowgen/testdata/zz_fixture_TestProwJobBaseBuilder_job_with_unresolved_config__including_podspec.yaml
  • pkg/prowgen/podspec.go
  • cmd/in-repo-config-plugin/bootstrap.go
  • pkg/api/types.go
  • pkg/prowgen/jobbase_test.go
  • cmd/in-repo-config-plugin/main.go
  • cmd/ci-operator-prowgen/main.go

Comment on lines +118 to +127
newJobNames, newPresubmits, newPeriodics := filterNewJobs(allJobs, s.jobConfigDir, org, repo, logger)

if len(newJobNames) == 0 {
logger.Info("no new tests detected, skipping ephemeral write")
return
}

lock := s.repoLock(org, repo)
lock.Lock()
defer lock.Unlock()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Existing-job read happens outside the repo lock.

filterNewJobs reads permanent job state via jc.ReadFromDir (Line 118) before s.repoLock is acquired (Lines 125-127). A concurrent handlePush writing via WriteBranchToDir under the same lock could race with this read, causing stale "already exists"/"new" decisions.

🤖 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 `@cmd/in-repo-config-plugin/server.go` around lines 118 - 127, Acquire the
repository lock before calling filterNewJobs so its jc.ReadFromDir state read is
serialized with handlePush’s WriteBranchToDir writes. Move the repoLock
acquisition and deferred unlock ahead of the filterNewJobs call, while
preserving the existing no-new-tests early return within the locked section.

Comment on lines +162 to +220
for _, job := range newPresubmits {
if job.Cluster == "" {
job.Cluster = kube.DefaultClusterAlias
}
if job.Namespace == nil || *job.Namespace == "" {
ns := prowCfg.PodNamespace
job.Namespace = &ns
}
if dc := prowCfg.Plank.GuessDefaultDecorationConfig(orgrepo, job.Cluster); dc != nil {
if job.DecorationConfig != nil {
job.DecorationConfig = dc.ApplyDefault(job.DecorationConfig)
} else {
job.DecorationConfig = dc
}
}
pj := pjutil.NewProwJob(pjutil.PresubmitSpec(job, refs), job.Labels, job.Annotations)
pj.Namespace = s.namespace
if err := s.pjclient.Create(context.Background(), &pj); err != nil {
logger.WithError(err).WithField("job", job.Name).Error("could not create ProwJob")
continue
}
triggered = append(triggered, job.Name)
}
for _, job := range newPeriodics {
var extraRefs []pjapi.Refs
for _, ref := range job.ExtraRefs {
if ref.Org == org && ref.Repo == repo {
continue
}
extraRefs = append(extraRefs, ref)
}
job.ExtraRefs = extraRefs
testName := periodicTestName(job.Name, org, repo, branch)
presubmit := prowconfig.Presubmit{
JobBase: job.JobBase,
Reporter: prowconfig.Reporter{Context: fmt.Sprintf("ci/prow/%s", testName)},
}
if presubmit.Cluster == "" {
presubmit.Cluster = kube.DefaultClusterAlias
}
if presubmit.Namespace == nil || *presubmit.Namespace == "" {
ns := prowCfg.PodNamespace
presubmit.Namespace = &ns
}
if dc := prowCfg.Plank.GuessDefaultDecorationConfig(orgrepo, presubmit.Cluster); dc != nil {
if presubmit.DecorationConfig != nil {
presubmit.DecorationConfig = dc.ApplyDefault(presubmit.DecorationConfig)
} else {
presubmit.DecorationConfig = dc
}
}
pj := pjutil.NewProwJob(pjutil.PresubmitSpec(presubmit, refs), presubmit.Labels, presubmit.Annotations)
pj.Namespace = s.namespace
if err := s.pjclient.Create(context.Background(), &pj); err != nil {
logger.WithError(err).WithField("job", job.Name).Error("could not create ProwJob for periodic")
continue
}
triggered = append(triggered, job.Name)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether the controller-runtime client is built with a timeout-bearing rest.Config
rg -n -A5 -B5 'ctrlruntimeclient.New|rest.Config' cmd/in-repo-config-plugin/main.go

Repository: openshift/ci-tools

Length of output: 554


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect how clusterConfig is built and whether a timeout is set anywhere nearby.
sed -n '1,260p' cmd/in-repo-config-plugin/main.go

echo '---'
rg -n 'Timeout|timeout|rest\.Config|clusterConfig|NewForConfig|New\(' cmd/in-repo-config-plugin -g '*.go'

Repository: openshift/ci-tools

Length of output: 10470


🌐 Web query:

kubernetes client-go rest.Config Timeout context.Background controller-runtime client.Create no deadline

💡 Result:

When using controller-runtime's client, passing context.Background—which has no deadline—does not inherently mean your Kubernetes request will wait indefinitely [1][2]. The effective timeout behavior is determined by the interaction between the context passed to the method (e.g., client.Create) and the configuration of the underlying rest.Config [3][4]. 1. Rest Config Timeout: The Timeout field in rest.Config is an optional setting for the underlying HTTP client [3][5]. If this value is greater than zero, it sets a hard limit on the duration of a request [3][4]. Even if you pass context.Background to a client call, the client-go library will often wrap the context using context.WithTimeout based on the value configured in rest.Config [4]. 2. Context Inheritance: When you invoke a method like client.Create(ctx, obj), the provided ctx is the primary driver for request cancellation [6][7]. If you pass context.Background, you are not providing a deadline, meaning the request will rely on other mechanisms for termination, such as: - The rest.Config.Timeout, if it is set to a non-zero value [4]. - The underlying network transport timeouts or server-side timeouts. 3. Best Practice: While passing context.Background is syntactically valid, it is generally recommended to pass a context with a timeout or cancellation signal (e.g., context.WithTimeout(context.Background, timeout)) to ensure that your application can recover from stalled requests or unresponsive API servers, rather than relying solely on the global rest.Config default [4]. In summary, passing context.Background does not guarantee an infinite wait if rest.Config.Timeout is configured; it simply delegates the timeout responsibility to the client configuration or network layer [4].

Citations:


Add a request deadline to ProwJob creation

pjclient.Create(context.Background(), &pj) still has no deadline here, and the in-cluster client config does not set one. A stalled API server can hold the handler goroutine and concurrency slot open indefinitely; use a bounded context for both Create calls.

🤖 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 `@cmd/in-repo-config-plugin/server.go` around lines 162 - 220, Add a bounded
timeout context for ProwJob creation and use it for both s.pjclient.Create calls
in the newPresubmits and newPeriodics loops. Replace each context.Background()
passed to Create with the deadline-bearing context, ensuring stalled API
requests cannot block indefinitely.

Comment on lines +343 to +389
func filterNewJobs(allJobs *prowconfig.JobConfig, jobConfigDir, org, repo string, logger *logrus.Entry) ([]string, []prowconfig.Presubmit, []prowconfig.Periodic) {
existingNames := map[string]bool{}
permanentPath := filepath.Join(jobConfigDir, org, repo)
if dirExists(permanentPath) {
existing, err := jc.ReadFromDir(permanentPath)
if err != nil {
logger.WithError(err).Warn("could not read existing jobs from EFS")
} else {
for _, jobs := range existing.PresubmitsStatic {
for _, j := range jobs {
existingNames[j.Name] = true
}
}
for _, jobs := range existing.PostsubmitsStatic {
for _, j := range jobs {
existingNames[j.Name] = true
}
}
for _, j := range existing.Periodics {
existingNames[j.Name] = true
}
}
}

var newJobNames []string
var newPresubmits []prowconfig.Presubmit
var newPeriodics []prowconfig.Periodic
orgrepo := fmt.Sprintf("%s/%s", org, repo)
for _, j := range allJobs.PresubmitsStatic[orgrepo] {
if !existingNames[j.Name] {
newJobNames = append(newJobNames, j.Name)
newPresubmits = append(newPresubmits, j)
}
}
for _, j := range allJobs.PostsubmitsStatic[orgrepo] {
if !existingNames[j.Name] {
newJobNames = append(newJobNames, j.Name)
}
}
for _, j := range allJobs.Periodics {
if !existingNames[j.Name] {
newJobNames = append(newJobNames, j.Name)
newPeriodics = append(newPeriodics, j)
}
}
return newJobNames, newPresubmits, newPeriodics
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Postsubmit jobs are silently dropped from ephemeral detection.

filterNewJobs counts new postsubmit names into newJobNames (Lines 377-381) but never returns them — there's no newPostsubmits slice. Downstream, handlePROpenedOrUpdated (Lines 118-140) builds ephemeralJobs.PostsubmitsStatic as an always-empty map, so a PR whose only new test is a postsubmit will pass the len(newJobNames) == 0 gate, write an ephemeral file with no presubmits/periodics, and post a "New tests detected" comment (Lines 222-229) that lists nothing.

🐛 Proposed fix
-func filterNewJobs(allJobs *prowconfig.JobConfig, jobConfigDir, org, repo string, logger *logrus.Entry) ([]string, []prowconfig.Presubmit, []prowconfig.Periodic) {
+func filterNewJobs(allJobs *prowconfig.JobConfig, jobConfigDir, org, repo string, logger *logrus.Entry) ([]string, []prowconfig.Presubmit, []prowconfig.Postsubmit, []prowconfig.Periodic) {
 	...
+	var newPostsubmits []prowconfig.Postsubmit
 	for _, j := range allJobs.PostsubmitsStatic[orgrepo] {
 		if !existingNames[j.Name] {
 			newJobNames = append(newJobNames, j.Name)
+			newPostsubmits = append(newPostsubmits, j)
 		}
 	}
 	...
-	return newJobNames, newPresubmits, newPeriodics
+	return newJobNames, newPresubmits, newPostsubmits, newPeriodics
 }

Then thread newPostsubmits into ephemeralJobs.PostsubmitsStatic[orgrepo] and into the comment listing at the call site.

🤖 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 `@cmd/in-repo-config-plugin/server.go` around lines 343 - 389, Update
filterNewJobs to collect newly detected postsubmit jobs in a newPostsubmits
slice and return it alongside the existing results. Thread that slice through
handlePROpenedOrUpdated into ephemeralJobs.PostsubmitsStatic[orgrepo], and
include the postsubmit names in the “New tests detected” comment listing so
postsubmit-only changes are preserved and reported.

Comment on lines +391 to +395
func (s *server) fetchConfigs(org, repo, sha string, l *logrus.Entry) (map[string]*cioperatorapi.ReleaseBuildConfiguration, error) {
entries, err := s.ghc.GetDirectory(org, repo, ciOperatorDir, sha)
if err != nil {
return s.fetchSingleConfig(org, repo, sha, l)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
printf '--- server.go around fetchConfigs ---\n'
sed -n '360,430p' cmd/in-repo-config-plugin/server.go
printf '\n--- search for fetchSingleConfig and FileNotFound ---\n'
rg -n "fetchSingleConfig|FileNotFound|GetDirectory\\(" cmd/in-repo-config-plugin -S
printf '\n--- tests in cmd/in-repo-config-plugin ---\n'
find cmd/in-repo-config-plugin -maxdepth 2 -type f | sort

Repository: openshift/ci-tools

Length of output: 247


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '380,420p' cmd/in-repo-config-plugin/server.go
printf '\n---\n'
rg -n "type FileNotFound|FileNotFound struct|GetDirectory\\(|fetchSingleConfig" cmd/in-repo-config-plugin -S
printf '\n--- tests ---\n'
rg -n "GetDirectory|FileNotFound|fetchConfigs|fetchSingleConfig" cmd/in-repo-config-plugin -g '*_test.go' -S

Repository: openshift/ci-tools

Length of output: 2297


🏁 Script executed:

#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
p = Path('cmd/in-repo-config-plugin/server.go')
text = p.read_text()
for needle in ['fetchConfigs', 'fetchSingleConfig', 'GetDirectory(', 'FileNotFound']:
    idx = text.find(needle)
    print(f'needle={needle!r} idx={idx}')
    if idx != -1:
        start = max(0, text.rfind('\n', 0, idx-200))
        end = text.find('\n', idx+300)
        print(text[start:end])
        print('---')
PY

Repository: openshift/ci-tools

Length of output: 1979


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '427,520p' cmd/in-repo-config-plugin/server.go
printf '\n--- server_test.go relevant section ---\n'
sed -n '1,140p' cmd/in-repo-config-plugin/server_test.go

Repository: openshift/ci-tools

Length of output: 6040


Only fall back on missing .ci-operator/
fetchConfigs should return the GetDirectory error unless it is github.FileNotFound; otherwise auth, rate-limit, and other API failures get masked by the .ci-operator.yaml fallback.

🤖 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 `@cmd/in-repo-config-plugin/server.go` around lines 391 - 395, Update
fetchConfigs so it calls fetchSingleConfig only when GetDirectory returns
github.FileNotFound; return the original GetDirectory error for authentication,
rate-limit, and other failures. Preserve the existing successful
directory-processing path and use the repository’s existing GitHub error symbols
or matching mechanism.

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

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant