Skip to content

Using a shell function to mirror instead of repeating the code#5249

Open
hector-vido wants to merge 1 commit into
openshift:mainfrom
hector-vido:promotion-shell-squash
Open

Using a shell function to mirror instead of repeating the code#5249
hector-vido wants to merge 1 commit into
openshift:mainfrom
hector-vido:promotion-shell-squash

Conversation

@hector-vido

@hector-vido hector-vido commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

The objective of these modifications is to reduce the total size of shell arguments and environment variables.

Promotion Step Shell Script Refactoring (summary)

This PR refactors how the promotion step builds the shell script run in the promotion Pod by introducing a reusable shell helper to perform image mirroring and by consolidating per-tag tagging logic into loop helpers. The change reduces repeated inline shell code, shrinking command/argument size and making retry/backoff behavior consistent across mirror operations.

What changed in practical terms

  • Component affected: ci-operator promotion step (pkg/steps/release).
  • A parameterized shell function (mirror) is injected at the start of the promotion container script and used for all quay mirroring calls instead of duplicating retry loops inline.
  • Mirroring is now performed per-image (one mirror call per source→destination) rather than batching multiple references into a single oc image mirror invocation.
  • Tagging of non-official ImageStream tags was refactored to use a small loop helper that reads tag mappings line-by-line and runs oc tag per entry inside the loop, with the retry wrapper applied at the loop level.
  • Test fixtures under pkg/steps/release/testdata were updated to reflect the new helper-based scripts; tests were adjusted to assert the new mirror invocation form.

Behavioral and operational impact for CI users/operators

  • Smaller shell argument and environment payloads for promotion Pods, reducing pressure on command-line length limits and env-var sizing.
  • Mirroring retry/backoff behavior is centralized and consistent across all images. (The mirror helper retains configured retry/backoff semantics used previously in most fixtures.)
  • Mirrors are executed per-image, producing clearer, per-image logs and retry attempts (easier to diagnose a single-image failure).
  • Tagging logic now uses a loop-based approach inside a retry wrapper; tagging retries were consolidated into a two-attempt retry wrapper in the quay tagging flow fixtures.
  • Maintainability improved: future changes to retry counts, backoff or logging need to be made in one place.

Tests and fixtures

  • Promotion-related fixtures and tests were updated to use the mirror helper and the new tag-loop helper (basic, arm64, multi-arch, quay, 4.12, multiple-tags, non-release fixtures).
  • Tests were simplified to validate the new mirror invocation pattern and the presence of the helper-based retry behavior.

Notes

  • No exported/public Go APIs were changed; changes are confined to the generation of the promotion Pod shell script and the associated test fixtures.

@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 Jun 12, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR consolidates image mirroring and tag import retry logic by extracting hardcoded shell loops into parameterized mirror and tag-read helper functions. The Go changes add getTagLoopCommand and mirrorShellFunction, integrate them into quay-step promotion pod assembly, and update tests and YAML fixtures to call the new helpers.

Changes

Promotion image mirroring and tagging refactor

Layer / File(s) Summary
Per-tag tag-import loop
pkg/steps/release/promote.go
Adds getTagLoopCommand that emits a heredoc-driven while read loop to run oc tag per-line instead of batching tags into one oc tag invocation.
Mirror helper and fixtures/tests
pkg/steps/release/promote.go, pkg/steps/release/promote_test.go, pkg/steps/release/testdata/*
Introduces mirrorShellFunction and refactors getMirrorRetryShell to emit per-image mirror calls. Updates TestGetMirrorRetryShell and all promotion YAML fixtures to use a parameterized mirror helper (retry/backoff, loglevel, registry-config, single src=dst).
Pod args/command assembly
pkg/steps/release/promote.go
Reorders promotion Pod command/argument construction so registryConfig and the /bin/sh -c wrapper are set up before building the commands slice; ensures mirrorShellFunction is the first script fragment.
Quay-step tag retry wiring
pkg/steps/release/promote.go, pkg/steps/release/testdata/zz_fixture_TestGetPromotionPod_promotion_quay*.yaml
Replaces previous “all together” + per-tag retry sequences with retryLoopTemplate that executes the heredoc-driven getTagLoopCommand per retry attempt; consolidates tag-retry wiring into the new loop-based approach.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 15 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% 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 pkg/steps/release/promote.go dereferences s.jobSpec in run() and s.pushSecret.Data / s.configuration.PromotionConfiguration in ensureNamespaces() without any nil checks. Add nil guards (or enforce non-nil in Validate/constructor and return an error) before dereferencing s.jobSpec, s.configuration, and s.pushSecret.Data.
✅ Passed checks (15 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main objective of the changeset: refactoring shell code to use a reusable mirror function instead of duplicating inline retry logic across multiple test fixtures and main code.
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.
Test Coverage For New Features ✅ Passed TestGetMirrorRetryShell unit-tests getMirrorRetryShell, and TestGetPromotionPod fixture comparisons assert the new mirror/tag shell-loop logic in generated pod args.
Stable And Deterministic Test Names ✅ Passed Promote/create/snapshot release test files contain no Ginkgo It/Describe/Context/When titles, and repo search found no ginkgo/v2 imports—no unstable Ginkgo names to flag.
Test Structure And Quality ✅ Passed No Ginkgo test code exists in this repo (no ginkgo/gomega imports or BeforeEach/AfterEach blocks); the PR’s changed tests are Go unit tests with single-purpose assertions and no cluster resource cr...
Microshift Test Compatibility ✅ Passed PR changes only promote.go/promotion pod shell fixtures and related unit tests; scanned those files—no Ginkgo e2e tests (g.It/Describe/Context/When) added or referencing MicroShift-incompatible APIs.
Single Node Openshift (Sno) Test Compatibility ✅ Passed PR #5249 changes only pkg/steps/release/promote.go, its unit test, and 7 fixtures; those files contain no Ginkgo/e2e test code (no g.It/Describe), so no new SNO multi-node assumptions were introduced.
Topology-Aware Scheduling Compatibility ✅ Passed PR changes promote.go shell logic/tests only; scanning promote.go and 7 promotion pod fixtures found no affinity/topologySpreadConstraints/PDB/control-plane scheduling labels—only kubernetes.io/arc...
Ote Binary Stdout Contract ✅ Passed Reviewed pkg/steps/release/promote.go and promote_test.go: no fmt.Print*/Printf/Println to stdout, and no klog stdout configuration in process-level code (only fmt.Errorf/fmt.Sprintf).
Ipv6 And Disconnected Network Test Compatibility ✅ Passed Searched repo for onsi/ginkgo/ginkgo usage: none found. This PR only updates promote.go and promotion shell fixtures—no new Ginkgo e2e tests or IPv4/external connectivity assumptions to flag.
No-Weak-Crypto ✅ Passed Searched promote.go, promote_test.go, and updated YAML fixtures for MD5/SHA1/DES/RC4/3DES/Blowfish/ECB/openssl/crypto and constant-time compare patterns; no weak-crypto matches found.
Container-Privileges ✅ Passed Scanned pkg/steps/release/promote.go, promote_test.go, and zz_fixture_TestGetPromotionPod*.yaml for privileged/hostPID/hostNetwork/hostIPC/SYS_ADMIN/allowPrivilegeEscalation/securityContext; no mat...
No-Sensitive-Data-In-Logs ✅ Passed mirrorShellFunction only echoes retry attempts/backoff (no secret/token/password output); Go logrus.Info/fixtures contain no Authorization/Bearer/password/token-like strings.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

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

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

@openshift-ci openshift-ci Bot requested review from deepsm007 and droslean June 12, 2026 17:10
@openshift-ci

openshift-ci Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: hector-vido

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 Jun 12, 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: 2

🤖 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 `@pkg/steps/release/promote_test.go`:
- Around line 1116-1119: The test currently only checks that the output contains
a substring (variable got vs shouldBe) which allows retry/fail behavior
regressions to pass; update the assertion to validate the full, exact generated
command and explicitly assert the retry semantics tokens (e.g., the "mirror 5 2
..." retry/count parameters) and any retry/fail markers produced by the helper
by replacing the contains check with an equality check against the fully
expected command string and/or additional assertions that inspect retry counts
or helper call outcomes (refer to the variables got and shouldBe and the "mirror
5 2 /etc/push-secret/.dockerconfigjson src=dst" token to locate the relevant
assertion to change).

In
`@pkg/steps/release/testdata/zz_fixture_TestGetPromotionPod_promotion_quay_non_release_namespace.yaml`:
- Around line 37-41: The retry loop around the oc tag command (the block using
"set +e" / "set -e" and "for r in {1..2}; do ... && break; :; done") currently
masks failures because the trailing ":" always returns success; update the loop
to detect exhaustion and fail explicitly: e.g., introduce a success flag
(tag_success) set when oc tag succeeds and after the loop check that flag and
exit non‑zero if unset, or replace the no-op ":" fallback with an explicit
non-zero exit when all attempts fail so the script does not continue silently;
modify the loop and add a post-loop failure check around the oc tag retry logic.
🪄 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: 0a1e7962-e5fe-4010-921e-2f5808cbf587

📥 Commits

Reviewing files that changed from the base of the PR and between 4b8b1c4 and 5245b4c.

📒 Files selected for processing (9)
  • pkg/steps/release/promote.go
  • pkg/steps/release/promote_test.go
  • pkg/steps/release/testdata/zz_fixture_TestGetPromotionPod_basic_case.yaml
  • pkg/steps/release/testdata/zz_fixture_TestGetPromotionPod_basic_case__arm64_only.yaml
  • pkg/steps/release/testdata/zz_fixture_TestGetPromotionPod_basic_case__multi_architecture.yaml
  • pkg/steps/release/testdata/zz_fixture_TestGetPromotionPod_promotion_quay.yaml
  • pkg/steps/release/testdata/zz_fixture_TestGetPromotionPod_promotion_quay_4.12.yaml
  • pkg/steps/release/testdata/zz_fixture_TestGetPromotionPod_promotion_quay_multiple_tags.yaml
  • pkg/steps/release/testdata/zz_fixture_TestGetPromotionPod_promotion_quay_non_release_namespace.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)

Comment thread pkg/steps/release/promote_test.go
@hector-vido hector-vido force-pushed the promotion-shell-squash branch from 5245b4c to 90b17da Compare June 12, 2026 23: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: 2

🤖 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
`@pkg/steps/release/testdata/zz_fixture_TestGetPromotionPod_basic_case__multi_architecture.yaml`:
- Around line 11-22: The generated shell `mirror` function uses brace expansion
`for r in {1..$1}` which fails when $1 is dynamic; update the generator in
mirrorShellFunction (in pkg/steps/release/promote.go) to emit a portable loop
such as `for r in $(seq 1 $1)` or a C-style loop `for ((r=1; r<=$1; r++))`
instead of the brace form so the produced fixtures (mirror function in the
YAMLs) iterate correctly; modify the string/template returned by
mirrorShellFunction accordingly and run tests to regenerate the affected
zz_fixture_* YAMLs.

In
`@pkg/steps/release/testdata/zz_fixture_TestGetPromotionPod_promotion_quay.yaml`:
- Around line 29-33: The retry stops too early because getTagLoopCommand's
generated tagCmd is the shell loop "while read tag; do oc tag ...; done <<EOF
..." whose exit code is only the last oc invocation; change getTagLoopCommand so
the loop accumulates failures (e.g., set a local status variable, run each oc
tag and if it fails set status=1) and after the loop run "exit $status" so
tagCmd returns non-zero if any oc tag failed; keep retryLoopTemplate using
"tagCmd && break" so retries only stop when the entire batch of tags succeeds.
🪄 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: 86c86060-88b1-4845-a30d-86f4ed3e7756

📥 Commits

Reviewing files that changed from the base of the PR and between 5245b4c and 90b17da.

📒 Files selected for processing (9)
  • pkg/steps/release/promote.go
  • pkg/steps/release/promote_test.go
  • pkg/steps/release/testdata/zz_fixture_TestGetPromotionPod_basic_case.yaml
  • pkg/steps/release/testdata/zz_fixture_TestGetPromotionPod_basic_case__arm64_only.yaml
  • pkg/steps/release/testdata/zz_fixture_TestGetPromotionPod_basic_case__multi_architecture.yaml
  • pkg/steps/release/testdata/zz_fixture_TestGetPromotionPod_promotion_quay.yaml
  • pkg/steps/release/testdata/zz_fixture_TestGetPromotionPod_promotion_quay_4.12.yaml
  • pkg/steps/release/testdata/zz_fixture_TestGetPromotionPod_promotion_quay_multiple_tags.yaml
  • pkg/steps/release/testdata/zz_fixture_TestGetPromotionPod_promotion_quay_non_release_namespace.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/steps/release/promote_test.go
  • pkg/steps/release/testdata/zz_fixture_TestGetPromotionPod_basic_case__arm64_only.yaml
  • pkg/steps/release/testdata/zz_fixture_TestGetPromotionPod_basic_case.yaml
  • pkg/steps/release/testdata/zz_fixture_TestGetPromotionPod_promotion_quay_multiple_tags.yaml
  • pkg/steps/release/promote.go
  • pkg/steps/release/testdata/zz_fixture_TestGetPromotionPod_promotion_quay_4.12.yaml
  • pkg/steps/release/testdata/zz_fixture_TestGetPromotionPod_promotion_quay_non_release_namespace.yaml

Comment on lines +11 to +22
function mirror {
for r in {1..$1}; do
echo Mirror attempt $r
if oc image mirror --loglevel=2 --keep-manifest-list --registry-config=/etc/push-secret/.dockerconfigjson --max-per-registry=10 docker-registry.default.svc:5000/ci-op-y2n8rsh3/pipeline@sha256:afd71aa3cbbf7d2e00cd8696747b2abf164700147723c657919c20b13d13ec62=registry.ci.openshift.org/ci/applyconfig:latest docker-registry.default.svc:5000/ci-op-y2n8rsh3/pipeline@sha256:bbb=registry.ci.openshift.org/ci/bin:latest; then break; fi
if [ "${r}" -eq 5 ]; then
if oc image mirror --loglevel=$2 --keep-manifest-list --registry-config=$3 --max-per-registry=10 $4; then break; fi
if [ "${r}" -eq $1 ]; then
exit 1
fi
backoff=$(($RANDOM % 120))s
echo Sleeping randomized $backoff before retry
sleep $backoff
done
}

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 | 🔴 Critical | ⚡ Quick win

Brace expansion bug in mirrorShellFunction (affects zz_fixture_TestGetPromotionPod_basic_case__multi_architecture.yaml and zz_fixture_TestGetPromotionPod_promotion_quay.yaml).

Both fixtures contain the same broken for r in {1..$1} loop from the generated mirror function. The fix must be in pkg/steps/release/promote.go where mirrorShellFunction is defined. Replace with $(seq 1 $1) or a C-style for loop.

🤖 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/steps/release/testdata/zz_fixture_TestGetPromotionPod_basic_case__multi_architecture.yaml`
around lines 11 - 22, The generated shell `mirror` function uses brace expansion
`for r in {1..$1}` which fails when $1 is dynamic; update the generator in
mirrorShellFunction (in pkg/steps/release/promote.go) to emit a portable loop
such as `for r in $(seq 1 $1)` or a C-style loop `for ((r=1; r<=$1; r++))`
instead of the brace form so the produced fixtures (mirror function in the
YAMLs) iterate correctly; modify the string/template returned by
mirrorShellFunction accordingly and run tests to regenerate the affected
zz_fixture_* YAMLs.

Comment on lines +29 to +33
quay.io/openshift/ci:ci_c_latest=quay.io/openshift/ci:ci_c_latest__post1\nfi\nfor
r in {1..2}; do echo \"Tag attempt $r\"; \nwhile read tag; do\noc tag --source=docker
--loglevel=2 --reference-policy='source' --import-mode='PreserveOriginal' --reference
$tag\ndone <<EOF\nquay-proxy.ci.openshift.org/openshift/ci@sha256:ddd ci/${component}-quay:c\nquay-proxy.ci.openshift.org/openshift/ci@sha256:bbb
ci/ci-quay:${component}\nEOF && break; :; done"

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how the tag loop is generated in promote.go
rg -n -A10 'getTagLoopCommand|retryLoopTemplate' pkg/steps/release/promote.go

Repository: openshift/ci-tools

Length of output: 1778


🏁 Script executed (no clone):

#!/bin/bash
set -u

run_case() {
  local label="$1"
  local inner_cmd="$2"   # evaluated inside loop; can refer to $tag
  echo "== $label =="
  # tagCmd similar to getTagLoopCommand output
  tagCmd=$(cat <<'EOF'
while read tag; do
  __INNER__
done <<'DATA'
one
two
three
DATA
EOF
)
  tagCmd="${tagCmd/__INNER__/$inner_cmd}"

  # capture exit status
  bash -c "$tagCmd"
  echo "exit=$?"
  echo
}

# 1) All succeed (true)
run_case "all succeed" "true"

# 2) fail for first tag, succeed for rest
run_case "fail first only" 'if [[ "$tag" == "one" ]]; then false; else true; fi'

# 3) fail for last tag
run_case "fail last only" 'if [[ "$tag" == "three" ]]; then false; else true; fi'

# 4) fail on any tag (always false)
run_case "always fail" "false"

Length of output: 142


Fix tag retry semantics: stop retrying only when all oc tag calls succeed.

retryLoopTemplate uses tagCmd && break, so whether retries stop depends on tagCmd’s exit status. getTagLoopCommand renders while read tag; do oc tag ...; done <<EOF ... EOF; in bash, the loop’s exit code is the exit status of the last oc tag run (EOF isn’t what makes it fail). That means if an earlier tag fails but later tags succeed, tagCmd exits 0 and the retry loop breaks—leaving the failed tags un-retried. Track failures across all tags (e.g., accumulate a non-zero status and exit $status) so any oc tag failure causes tagCmd to fail.

🤖 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/steps/release/testdata/zz_fixture_TestGetPromotionPod_promotion_quay.yaml`
around lines 29 - 33, The retry stops too early because getTagLoopCommand's
generated tagCmd is the shell loop "while read tag; do oc tag ...; done <<EOF
..." whose exit code is only the last oc invocation; change getTagLoopCommand so
the loop accumulates failures (e.g., set a local status variable, run each oc
tag and if it fails set status=1) and after the loop run "exit $status" so
tagCmd returns non-zero if any oc tag failed; keep retryLoopTemplate using
"tagCmd && break" so retries only stop when the entire batch of tags succeeds.

@openshift-ci

openshift-ci Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

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

Test name Commit Details Required Rerun command
ci/prow/integration 90b17da link true /test integration
ci/prow/breaking-changes 90b17da link false /test breaking-changes

Full PR test history. Your PR dashboard.

Details

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

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.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant