Skip to content

caib: add progress bar#125

Merged
bennyz merged 3 commits into
centos-automotive-suite:mainfrom
bennyz:progress
Feb 24, 2026
Merged

caib: add progress bar#125
bennyz merged 3 commits into
centos-automotive-suite:mainfrom
bennyz:progress

Conversation

@bennyz

@bennyz bennyz commented Feb 23, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Interactive terminal progress bar for builds with one-line fallback in non‑TTY environments.
    • Stage-based progress reporting surfaced for builder, pull/push, flash and finalization steps.
  • Changes

    • "--follow" now defaults to false for build-related commands; help text updated.
    • Build task timeout increased to 90 minutes.
    • Pipeline now supports inline builder preparation (prepare-builder removed) and clearer build error messages.
  • Permissions

    • Added RBAC resources to allow build-step progress annotations.

@coderabbitai

coderabbitai Bot commented Feb 23, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a server-side build progress API and schemas, client GetBuildProgress, pod-annotation progress emission in task scripts, CLI progress-bar rendering integrated into the build-wait flow, RBAC to permit pod patching, and pipeline/task wiring changes (remove prepare-builder, add rebuild-builder).

Changes

Cohort / File(s) Summary
Build API (server + client + schema)
internal/buildapi/progress.go, internal/buildapi/server.go, internal/buildapi/openapi.yaml, internal/buildapi/client/client.go
New GET /v1/builds/{name}/progress endpoint and schemas (BuildProgress, BuildStep); server aggregates pod-annotation progress with caching; client adds GetBuildProgress (returns nil on 404).
CLI progress UI
cmd/caib/main.go
Adds a progressBar type (TTY/plain) and integrates polling of GetBuildProgress into the build wait flow; defaults --follow to false for build commands and clears bar on completion/timeout.
Script instrumentation
internal/common/tasks/scripts/common.sh, internal/common/tasks/scripts/build_builder.sh, internal/common/tasks/scripts/build_image.sh, internal/common/tasks/scripts/push_artifact.sh, internal/common/tasks/scripts/flash_image.sh
Adds emit_progress(stage, done, total) to write pod annotations and emits progress markers at key build/push/flash stages; minor shell option and env defaults adjustments.
Embedded scripts wiring
internal/common/tasks/scripts.go, internal/common/tasks/scripts/common.sh
Adds go:embed for new scripts and initializes exported PushArtifactScript and FlashImageScript by concatenating commonScript with embedded scripts.
Tekton / pipeline tasks
internal/common/tasks/tasks.go
Removes prepare-builder Task, introduces rebuild-builder parameter, wires parameter through tasks, and increases build-image timeout to 90 minutes.
RBAC for pod annotation
config/rbac/pipeline_pod_role.yaml, config/rbac/pipeline_pod_rolebinding.yaml, config/rbac/kustomization.yaml
Adds Role and RoleBinding (pipeline-pod-annotator) and includes them in kustomization to permit pod patching for progress annotations.
Controller/operator adjustments
internal/controller/operatorconfig/controller.go, internal/controller/imagebuild/controller.go
Stops creating the prepare-builder Tekton task and updates comments to reflect inline builder preparation when builder-image is empty.
Docs / skills file tweak
.claude/skills/test-bootc-build/SKILL.md
Minor error-pattern text edits and two new error entries.

Sequence Diagram

sequenceDiagram
    participant CLI as CLI / User
    participant API as API Server
    participant K8s as Kubernetes (Tekton / Pods)
    participant Scripts as Build Scripts

    CLI->>API: POST /v1/builds/{name} (start build)
    API->>K8s: Create PipelineRun / TaskRuns
    K8s->>Scripts: Execute tasks (build scripts)
    Scripts->>K8s: emit_progress -> PATCH pod annotation

    loop Poll (when not following logs)
        CLI->>API: GET /v1/builds/{name}/progress
        API->>K8s: List pods / read annotations
        K8s-->>API: Pod annotations with progress markers
        API->>API: Aggregate -> BuildProgress
        API-->>CLI: BuildProgress {Phase, Step}
        CLI->>CLI: Render progressBar (TTY or plain)
    end

    K8s-->>API: PipelineRun completes
    CLI->>API: GET /v1/builds/{name} (final status)
    API-->>CLI: Final build result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested reviewers

  • bkhizgiy

Poem

🐰 I nudge the bar and hop with glee,
Pods send crumbs of progress to me,
Server tallies steps along the way,
CLI paints the path as builds convey,
Hooray — a carrot for each completed key!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 68.18% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'caib: add progress bar' accurately captures the main change - adding a progress bar subsystem to the caib (command-line interface) tool, which is reflected throughout the core changes in cmd/caib/main.go and supporting infrastructure changes.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@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 (3)
internal/buildapi/progress.go (1)

106-116: Consider using defer for stream closure to prevent resource leaks.

If scanner.Scan() or any future code between Stream() and Close() were to panic, the stream would leak. Wrapping the per-container log reading in a closure with defer stream.Close() is more idiomatic Go and safer.

♻️ Suggested refactor
-			stream, err := cs.CoreV1().Pods(namespace).GetLogs(pod.Name, logOpts).Stream(ctx)
-			if err != nil {
-				continue
-			}
-			scanner := bufio.NewScanner(stream)
-			for scanner.Scan() {
-				if step, ok := parseProgressMarker(scanner.Text()); ok {
-					latest = step
+			func() {
+				stream, err := cs.CoreV1().Pods(namespace).GetLogs(pod.Name, logOpts).Stream(ctx)
+				if err != nil {
+					return
 				}
-			}
-			_ = stream.Close()
+				defer stream.Close()
+				scanner := bufio.NewScanner(stream)
+				for scanner.Scan() {
+					if step, ok := parseProgressMarker(scanner.Text()); ok {
+						latest = step
+					}
+				}
+			}()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/buildapi/progress.go` around lines 106 - 116, The per-pod log stream
opened with cs.CoreV1().Pods(...).GetLogs(...).Stream(ctx) is closed explicitly
with _ = stream.Close(), which can leak if scanner.Scan() panics or returns
early; wrap the code that reads the stream in an inline closure (e.g., anonymous
func() { ... }()) immediately after successfully obtaining stream, call defer
stream.Close() at the top of that closure, and move the
bufio.NewScanner(stream)/for scanner.Scan() logic inside it (also handle
scanner.Err() if present) and remove the explicit _ = stream.Close() call; this
applies to the code that uses parseProgressMarker and updates latest.
internal/common/tasks/scripts/build_image.sh (2)

321-333: Heartbeat-while-wait pattern is repeated four times — consider extracting a helper.

The same background-heartbeat + wait + kill pattern appears for the bootc build (lines 322–333), image/package build (lines 409–420), disk build (lines 449–460), and builder preparation (lines 233–244). A helper function like run_with_heartbeat <pid> <stage> <step> <total> would reduce duplication and lower the risk of copy-paste mistakes.

♻️ Example helper
# Run a heartbeat loop while a background process is alive.
# Usage: run_with_heartbeat <pid> <stage> <step> <total>
run_with_heartbeat() {
  local pid="$1" stage="$2" step="$3" total="$4"
  while kill -0 "$pid" 2>/dev/null; do
    emit_progress "$stage" "$step" "$total"
    sleep 30
  done &
  local hb_pid=$!
  wait "$pid"
  local rc=$?
  kill "$hb_pid" 2>/dev/null; wait "$hb_pid" 2>/dev/null || true
  return "$rc"
}

Then each call site simplifies to:

aib --verbose build ... &
AIB_PID=$!
run_with_heartbeat "$AIB_PID" "Building image" "$STEP_BUILD" "$PROGRESS_TOTAL" || exit $?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/common/tasks/scripts/build_image.sh` around lines 321 - 333, Extract
the repeated background-heartbeat + wait + kill logic into a helper function
named run_with_heartbeat that accepts the background PID, stage message, step
variable and total variable (e.g., run_with_heartbeat <pid> <stage> <step>
<total>); implement it to run the emit_progress loop while kill -0 "$pid"
succeeds (sleep 30), spawn that loop in background, wait for the target pid,
capture its exit code, kill and wait the heartbeat pid, and return the captured
exit code. Replace each duplicated block that currently uses patterns with
variables like AIB_PID, BOOTC_PID, IMAGE_PID, BUILDER_PREPARE_PID (or similarly
named PIDs) with a call to run_with_heartbeat "$PID" "Building image"/"Booting
build"/... "$STEP_BUILD" "$PROGRESS_TOTAL" and propagate non-zero returns with
exit $?. Ensure the helper uses local variables for pid/hb_pid/rc and that
callers use the helper's return code to decide exit.

3-8: emit_progress is duplicated across four scripts, but centralizing it in common.sh won't work for all of them.

The function is defined in build_image.sh, build_builder.sh, flash_image.sh, and push_artifact.sh. However, common.sh is only prepended to build_image.sh and build_builder.sh at embed time (as stated in its header and confirmed by the init() function). The push_artifact.sh and flash_image.sh scripts are embedded independently and do not include common.sh.

Additionally, push_artifact.sh omits the local keyword in its emit_progress function (stage="$1"; done="$2"; total="$3"), which causes those variables to leak into the calling scope. The other three scripts correctly use local.

To address this, either:

  1. Prepend common.sh to all four scripts in the embedding logic and move emit_progress to common.sh, or
  2. Accept the duplication as necessary given the current architecture and add the local keyword to push_artifact.sh for consistency.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/common/tasks/scripts/build_image.sh` around lines 3 - 8, The
emit_progress function is duplicated across build_image.sh, build_builder.sh,
flash_image.sh, and push_artifact.sh and push_artifact.sh's implementation leaks
variables (missing local); either prepend common.sh into the embedding process
for all four scripts and move emit_progress into common.sh (remove duplicates
from build_image.sh, build_builder.sh, flash_image.sh, push_artifact.sh), or
keep duplication but update the emit_progress in push_artifact.sh to declare its
parameters with local (e.g., local stage="$1" local done="$2" local total="$3")
so variables do not leak; locate the function by name emit_progress to apply the
change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cmd/caib/main.go`:
- Around line 1847-1853: Clamp the progress values before building the bar:
ensure filled is computed as an int bounded between 0 and barWidth (e.g., filled
= max(0, min(barWidth, barWidth*step.Done/step.Total))) and compute remaining =
barWidth - filled (guaranteed non-negative) before calling strings.Repeat so
strings.Repeat never receives a negative count; update the block that sets
barWidth, filled, and bar (referencing variables barWidth, filled, step.Done,
step.Total and the strings.Repeat calls) to perform this clamping.

---

Nitpick comments:
In `@internal/buildapi/progress.go`:
- Around line 106-116: The per-pod log stream opened with
cs.CoreV1().Pods(...).GetLogs(...).Stream(ctx) is closed explicitly with _ =
stream.Close(), which can leak if scanner.Scan() panics or returns early; wrap
the code that reads the stream in an inline closure (e.g., anonymous func() {
... }()) immediately after successfully obtaining stream, call defer
stream.Close() at the top of that closure, and move the
bufio.NewScanner(stream)/for scanner.Scan() logic inside it (also handle
scanner.Err() if present) and remove the explicit _ = stream.Close() call; this
applies to the code that uses parseProgressMarker and updates latest.

In `@internal/common/tasks/scripts/build_image.sh`:
- Around line 321-333: Extract the repeated background-heartbeat + wait + kill
logic into a helper function named run_with_heartbeat that accepts the
background PID, stage message, step variable and total variable (e.g.,
run_with_heartbeat <pid> <stage> <step> <total>); implement it to run the
emit_progress loop while kill -0 "$pid" succeeds (sleep 30), spawn that loop in
background, wait for the target pid, capture its exit code, kill and wait the
heartbeat pid, and return the captured exit code. Replace each duplicated block
that currently uses patterns with variables like AIB_PID, BOOTC_PID, IMAGE_PID,
BUILDER_PREPARE_PID (or similarly named PIDs) with a call to run_with_heartbeat
"$PID" "Building image"/"Booting build"/... "$STEP_BUILD" "$PROGRESS_TOTAL" and
propagate non-zero returns with exit $?. Ensure the helper uses local variables
for pid/hb_pid/rc and that callers use the helper's return code to decide exit.
- Around line 3-8: The emit_progress function is duplicated across
build_image.sh, build_builder.sh, flash_image.sh, and push_artifact.sh and
push_artifact.sh's implementation leaks variables (missing local); either
prepend common.sh into the embedding process for all four scripts and move
emit_progress into common.sh (remove duplicates from build_image.sh,
build_builder.sh, flash_image.sh, push_artifact.sh), or keep duplication but
update the emit_progress in push_artifact.sh to declare its parameters with
local (e.g., local stage="$1" local done="$2" local total="$3") so variables do
not leak; locate the function by name emit_progress to apply the change.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 37d6442 and 6951b70.

📒 Files selected for processing (10)
  • cmd/caib/main.go
  • internal/buildapi/client/client.go
  • internal/buildapi/openapi.yaml
  • internal/buildapi/progress.go
  • internal/buildapi/server.go
  • internal/common/tasks/scripts/build_builder.sh
  • internal/common/tasks/scripts/build_image.sh
  • internal/common/tasks/scripts/flash_image.sh
  • internal/common/tasks/scripts/push_artifact.sh
  • internal/common/tasks/tasks.go

Comment thread cmd/caib/main.go
Comment thread internal/buildapi/progress.go Outdated
if err != nil {
continue
}
scanner := bufio.NewScanner(stream)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

IIRC bufio.Scanner has a line limit, if a log line is longer we will get an error and we might miss later the progress markers.
maybe we should increase the scanner buffer and check scanner.Err() so progress doesn’t randomly stall.

Comment thread internal/buildapi/progress.go Outdated
Container: container.Name,
TailLines: &tailLines,
}
stream, err := cs.CoreV1().Pods(namespace).GetLogs(pod.Name, logOpts).Stream(ctx)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

we’re opening log streams for every step ,since /progress is likely polled often, wouldn't this add noticeable latency under load?

@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

🧹 Nitpick comments (1)
internal/common/tasks/scripts/build_builder.sh (1)

3-13: emit_progress is duplicated verbatim across four scripts.

This function is copy-pasted in build_builder.sh, build_image.sh, flash_image.sh, and push_artifact.sh. If the annotation key or the curl logic ever changes, all four copies must be updated in lockstep.

Consider extracting it into a shared helper (e.g., a file sourced at embed time, similar to how common.sh is prepended to build_builder.sh and build_image.sh). For scripts that don't use common.sh today, you could either extend the prepend mechanism or source a small standalone snippet.

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

In `@internal/common/tasks/scripts/build_builder.sh` around lines 3 - 13, The
emit_progress function is duplicated across multiple scripts; extract it into a
single shared helper (e.g., common_emit_progress.sh or add to common.sh) and
update each script that currently defines emit_progress to instead source that
helper at the top and call emit_progress as before; ensure the helper exposes
the same function name (emit_progress) and has the same behavior (uses
KUBERNETES_SERVICE_* env, HOSTNAME and serviceaccount files), and remove the
duplicate function definitions from build_builder.sh, build_image.sh,
flash_image.sh and push_artifact.sh so future changes are made in one place.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/common/tasks/scripts/build_image.sh`:
- Around line 469-471: The "Compressing artifacts" progress line is emitted
unconditionally even when no compression step is included (e.g.,
BUILD_DISK_IMAGE=false and BUILD_MODE=bootc), causing a misleading stage label;
wrap the emit_progress call so it only runs when the compression step is
actually counted (mirror the same condition used to add the compression step to
PROGRESS_TOTAL), i.e., check the build flags/values (BUILD_DISK_IMAGE and/or
BUILD_MODE) before calling emit_progress "Compressing artifacts" with
STEP_FINALIZE and PROGRESS_TOTAL so the label is only emitted when compression
will occur.

In `@internal/common/tasks/scripts/push_artifact.sh`:
- Around line 4-14: The script relies on the Bash-only HOSTNAME variable inside
emit_progress causing portability issues; fix emit_progress by explicitly
setting a safe pod name variable (e.g., resolve HOSTNAME at script start or
inside emit_progress using hostname command) and use that variable in the PATCH
URL instead of raw ${HOSTNAME}; update the top of the script or the
emit_progress function to assign HOSTNAME="$(hostname)" (or similar) so the
value is defined under /bin/sh before it’s referenced.

---

Nitpick comments:
In `@internal/common/tasks/scripts/build_builder.sh`:
- Around line 3-13: The emit_progress function is duplicated across multiple
scripts; extract it into a single shared helper (e.g., common_emit_progress.sh
or add to common.sh) and update each script that currently defines emit_progress
to instead source that helper at the top and call emit_progress as before;
ensure the helper exposes the same function name (emit_progress) and has the
same behavior (uses KUBERNETES_SERVICE_* env, HOSTNAME and serviceaccount
files), and remove the duplicate function definitions from build_builder.sh,
build_image.sh, flash_image.sh and push_artifact.sh so future changes are made
in one place.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6951b70 and e5121d0.

📒 Files selected for processing (16)
  • .claude/skills/test-bootc-build/SKILL.md
  • cmd/caib/main.go
  • config/rbac/kustomization.yaml
  • config/rbac/pipeline_pod_role.yaml
  • config/rbac/pipeline_pod_rolebinding.yaml
  • internal/buildapi/client/client.go
  • internal/buildapi/openapi.yaml
  • internal/buildapi/progress.go
  • internal/buildapi/server.go
  • internal/common/tasks/scripts/build_builder.sh
  • internal/common/tasks/scripts/build_image.sh
  • internal/common/tasks/scripts/flash_image.sh
  • internal/common/tasks/scripts/push_artifact.sh
  • internal/common/tasks/tasks.go
  • internal/controller/imagebuild/controller.go
  • internal/controller/operatorconfig/controller.go
💤 Files with no reviewable changes (1)
  • internal/controller/operatorconfig/controller.go
✅ Files skipped from review due to trivial changes (1)
  • internal/controller/imagebuild/controller.go

Comment thread internal/common/tasks/scripts/build_image.sh
Comment on lines +4 to +14
# Format: stage|done|total (pipe-delimited, stored as a pod annotation)
emit_progress() {
_ep_stage="$1"; _ep_done="$2"; _ep_total="$3"
curl -s --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
-X PATCH \
-H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
-H "Content-Type: application/merge-patch+json" \
"https://${KUBERNETES_SERVICE_HOST}:${KUBERNETES_SERVICE_PORT}/api/v1/namespaces/$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)/pods/${HOSTNAME}" \
-d "{\"metadata\":{\"annotations\":{\"automotive.sdv.cloud.redhat.com/progress\":\"${_ep_stage}|${_ep_done}|${_ep_total}\"}}}" \
> /dev/null 2>&1 || true
}

@coderabbitai coderabbitai Bot Feb 24, 2026

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

HOSTNAME is undefined in POSIX sh (SC3028).

The shebang is #!/bin/sh but HOSTNAME is a Bash-specific variable, not guaranteed by POSIX. In Kubernetes pods, HOSTNAME is typically set as an environment variable by the container runtime, so this will likely work in practice, but it's fragile.

Consider explicitly setting it at the top of the script or using $(hostname):

Suggested fix
 emit_progress() {
   _ep_stage="$1"; _ep_done="$2"; _ep_total="$3"
+  _ep_hostname="${HOSTNAME:-$(hostname)}"
   curl -s --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
     -X PATCH \
     -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
     -H "Content-Type: application/merge-patch+json" \
-    "https://${KUBERNETES_SERVICE_HOST}:${KUBERNETES_SERVICE_PORT}/api/v1/namespaces/$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)/pods/${HOSTNAME}" \
+    "https://${KUBERNETES_SERVICE_HOST}:${KUBERNETES_SERVICE_PORT}/api/v1/namespaces/$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)/pods/${_ep_hostname}" \
     -d "{\"metadata\":{\"annotations\":{\"automotive.sdv.cloud.redhat.com/progress\":\"${_ep_stage}|${_ep_done}|${_ep_total}\"}}}" \
     > /dev/null 2>&1 || true
 }
📝 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
# Format: stage|done|total (pipe-delimited, stored as a pod annotation)
emit_progress() {
_ep_stage="$1"; _ep_done="$2"; _ep_total="$3"
curl -s --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
-X PATCH \
-H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
-H "Content-Type: application/merge-patch+json" \
"https://${KUBERNETES_SERVICE_HOST}:${KUBERNETES_SERVICE_PORT}/api/v1/namespaces/$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)/pods/${HOSTNAME}" \
-d "{\"metadata\":{\"annotations\":{\"automotive.sdv.cloud.redhat.com/progress\":\"${_ep_stage}|${_ep_done}|${_ep_total}\"}}}" \
> /dev/null 2>&1 || true
}
# Format: stage|done|total (pipe-delimited, stored as a pod annotation)
emit_progress() {
_ep_stage="$1"; _ep_done="$2"; _ep_total="$3"
_ep_hostname="${HOSTNAME:-$(hostname)}"
curl -s --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
-X PATCH \
-H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
-H "Content-Type: application/merge-patch+json" \
"https://${KUBERNETES_SERVICE_HOST}:${KUBERNETES_SERVICE_PORT}/api/v1/namespaces/$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)/pods/${_ep_hostname}" \
-d "{\"metadata\":{\"annotations\":{\"automotive.sdv.cloud.redhat.com/progress\":\"${_ep_stage}|${_ep_done}|${_ep_total}\"}}}" \
> /dev/null 2>&1 || true
}
🧰 Tools
🪛 Shellcheck (0.11.0)

[warning] 11-11: In POSIX sh, HOSTNAME is undefined.

(SC3028)

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

In `@internal/common/tasks/scripts/push_artifact.sh` around lines 4 - 14, The
script relies on the Bash-only HOSTNAME variable inside emit_progress causing
portability issues; fix emit_progress by explicitly setting a safe pod name
variable (e.g., resolve HOSTNAME at script start or inside emit_progress using
hostname command) and use that variable in the PATCH URL instead of raw
${HOSTNAME}; update the top of the script or the emit_progress function to
assign HOSTNAME="$(hostname)" (or similar) so the value is defined under /bin/sh
before it’s referenced.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

this is set by k8s

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@bennyz, you're absolutely right! The hostname of a Container is the name of the Pod in which the Container is running, and Kubernetes automatically sets the HOSTNAME environment variable in pod containers. Since this script is specifically designed to run within Kubernetes pods (as evident from the service account token usage and Kubernetes API calls), the HOSTNAME variable will always be available.

The shellcheck warning is technically correct from a pure POSIX portability perspective, but it doesn't apply in your Kubernetes context. Thanks for the clarification!


✏️ Learnings added
Learnt from: bennyz
Repo: centos-automotive-suite/automotive-dev-operator PR: 125
File: internal/common/tasks/scripts/push_artifact.sh:4-14
Timestamp: 2026-02-24T12:35:48.161Z
Learning: In the centos-automotive-suite/automotive-dev-operator repository, shell scripts that run within Kubernetes pods (such as those in internal/common/tasks/scripts/) can safely rely on the HOSTNAME environment variable being set by Kubernetes, even when using #!/bin/sh. The HOSTNAME variable is automatically provided by Kubernetes and equals the pod name. Shellcheck warnings about HOSTNAME being undefined in POSIX sh (SC3028) can be ignored for scripts that are exclusively executed in Kubernetes pod containers.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: bennyz
Repo: centos-automotive-suite/automotive-dev-operator PR: 123
File: internal/common/tasks/scripts/common.sh:23-30
Timestamp: 2026-02-20T14:04:45.140Z
Learning: Guideline for Bash scripts: when reading values with IFS= read -r into a Bash array and expanding with "${ARRAY[@]}", shell metacharacters in values are not interpreted if used in a quoted array expansion, so injection risk from content is mitigated by word-splitting and expansion being avoided. In reviews, focus on the format of assignments (KEY=VALUE) rather than restricting allowed characters, unless the value will be used in an unquoted context or via eval. This pattern applies broadly to Bash scripts in the repo; ensure such assignment formatting is followed and that values are safely handled when not within quoted contexts.

Since both have a lot of overlap, it makes sense to consolidate them
and reduce the pod start overhead

Signed-off-by: Benny Zlotnik <bzlotnik@redhat.com>
Assisted-by: gpt-5.3
to avoid redownloading RPMs use --cache --build-dir for build-builder

Signed-off-by: Benny Zlotnik <bzlotnik@redhat.com>
Assisted-by: gpt-5.3

@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: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@config/rbac/kustomization.yaml`:
- Around line 32-33: Change the pipeline_pod_role.yaml to define a
namespace-scoped Role instead of a ClusterRole by setting kind: Role and adding
metadata.namespace: system, keeping the same rules and metadata.name; then
update pipeline_pod_rolebinding.yaml to point roleRef.kind: Role (instead of
ClusterRole) so the RoleBinding in the system namespace correctly references the
namespaced Role. Ensure the Role name used in metadata.name matches the
roleRef.name in the RoleBinding.

In `@internal/buildapi/progress.go`:
- Around line 84-97: parseProgressAnnotation currently accepts negative integer
values which can corrupt aggregate progress; update the function
(parseProgressAnnotation) to validate the parsed integers and reject any
negative values by returning nil,false when done < 0 or total < 0 (and
optionally when total == 0 if you want to avoid divide-by-zero downstream). Keep
the existing error paths for parse failures and only return a BuildStep{Stage:
parts[0], Done: done, Total: total} when both integers are non-negative (and
total > 0 if chosen).

In `@internal/common/tasks/scripts/common.sh`:
- Around line 5-16: The emit_progress function can hang because curl has no
timeouts; update the curl invocation inside emit_progress to include connection
and total operation time limits (e.g., add --connect-timeout and --max-time
flags) so the PATCH call will fail fast on DNS/TCP/TLS stalls or long responses;
modify the curl command in emit_progress to pass appropriate timeout values that
match your cluster SLA and keep the existing error-suppression behavior (the
trailing || true).

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fe46ea2 and 224cb1f.

📒 Files selected for processing (18)
  • .claude/skills/test-bootc-build/SKILL.md
  • cmd/caib/main.go
  • config/rbac/kustomization.yaml
  • config/rbac/pipeline_pod_role.yaml
  • config/rbac/pipeline_pod_rolebinding.yaml
  • internal/buildapi/client/client.go
  • internal/buildapi/openapi.yaml
  • internal/buildapi/progress.go
  • internal/buildapi/server.go
  • internal/common/tasks/scripts.go
  • internal/common/tasks/scripts/build_builder.sh
  • internal/common/tasks/scripts/build_image.sh
  • internal/common/tasks/scripts/common.sh
  • internal/common/tasks/scripts/flash_image.sh
  • internal/common/tasks/scripts/push_artifact.sh
  • internal/common/tasks/tasks.go
  • internal/controller/imagebuild/controller.go
  • internal/controller/operatorconfig/controller.go
💤 Files with no reviewable changes (1)
  • internal/controller/operatorconfig/controller.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • config/rbac/pipeline_pod_rolebinding.yaml
  • internal/controller/imagebuild/controller.go
  • internal/buildapi/server.go
  • config/rbac/pipeline_pod_role.yaml

Comment on lines +32 to +33
- pipeline_pod_role.yaml
- pipeline_pod_rolebinding.yaml

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Locate and inspect the RBAC files mentioned in the review

echo "=== Searching for pipeline_pod_role.yaml ==="
fd -e yaml 'pipeline_pod_role' -exec cat {} \;

echo ""
echo "=== Searching for pipeline_pod_rolebinding.yaml ==="
fd -e yaml 'pipeline_pod_rolebinding' -exec cat {} \;

Repository: centos-automotive-suite/automotive-dev-operator

Length of output: 581


🏁 Script executed:

#!/bin/bash
# Locate and inspect the RBAC files with corrected syntax

echo "=== Searching for pipeline_pod_role.yaml ==="
find . -name "pipeline_pod_role.yaml" -type f | head -20

echo ""
echo "=== Searching for pipeline_pod_rolebinding.yaml ==="
find . -name "pipeline_pod_rolebinding.yaml" -type f | head -20

echo ""
echo "=== Listing files in config/rbac directory ==="
ls -la config/rbac/ 2>/dev/null || echo "Directory not found"

Repository: centos-automotive-suite/automotive-dev-operator

Length of output: 1764


🏁 Script executed:

#!/bin/bash
# Read the actual content of both RBAC files

echo "=== Content of pipeline_pod_role.yaml ==="
cat config/rbac/pipeline_pod_role.yaml

echo ""
echo "=== Content of pipeline_pod_rolebinding.yaml ==="
cat config/rbac/pipeline_pod_rolebinding.yaml

Repository: centos-automotive-suite/automotive-dev-operator

Length of output: 636


Use a namespace-scoped Role instead of ClusterRole for pod patching.

The pipeline_pod_role.yaml file currently defines a ClusterRole, but since it's bound via a RoleBinding scoped to the system namespace, a namespace-scoped Role is more appropriate. While the effective permissions are limited to the namespace, the ClusterRole object exists cluster-wide and can be rebound elsewhere by any cluster-admin, unnecessarily broadening the attack surface. This violates the principle of least privilege.

Change pipeline_pod_role.yaml to use kind: Role and add the namespace: system metadata field, then update the roleRef.kind in pipeline_pod_rolebinding.yaml from ClusterRole to Role.

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

In `@config/rbac/kustomization.yaml` around lines 32 - 33, Change the
pipeline_pod_role.yaml to define a namespace-scoped Role instead of a
ClusterRole by setting kind: Role and adding metadata.namespace: system, keeping
the same rules and metadata.name; then update pipeline_pod_rolebinding.yaml to
point roleRef.kind: Role (instead of ClusterRole) so the RoleBinding in the
system namespace correctly references the namespaced Role. Ensure the Role name
used in metadata.name matches the roleRef.name in the RoleBinding.

Comment thread internal/buildapi/progress.go
Comment thread internal/common/tasks/scripts/common.sh
use annotations to report the progress of a build

Signed-off-by: Benny Zlotnik <bzlotnik@redhat.com>
Assisted-by: gpt-5.3

@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

♻️ Duplicate comments (1)
internal/buildapi/progress.go (1)

82-97: ⚠️ Potential issue | 🟡 Minor

Reject negative progress values from pod annotations.

Negative done/total can flow into pipeline totals and produce misleading progress. Guard during parsing.

🛡️ Suggested guard
 done, err := strconv.Atoi(parts[1])
-if err != nil {
+if err != nil || done < 0 {
 	return nil, false
 }
 total, err := strconv.Atoi(parts[2])
-if err != nil {
+if err != nil || total < 0 {
 	return nil, false
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/buildapi/progress.go` around lines 82 - 97, The
parseProgressAnnotation function currently accepts negative numeric values;
update it to validate parsed integers and reject negatives by returning
nil,false when either done or total is negative (i.e., after strconv.Atoi for
parts[1] and parts[2] check if done < 0 || total < 0 and return nil,false). Keep
using the same function and BuildStep struct; no other behavior changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@config/rbac/pipeline_pod_role.yaml`:
- Around line 1-8: The Role resource "pipeline-pod-annotator" is
namespace-scoped but has no metadata.namespace, so the RoleBinding in namespace
"system" will not resolve; update the Role manifest (resource named
pipeline-pod-annotator) to include metadata.namespace: system (or confirm
kustomize injection) so the Role and the RoleBinding target the same namespace
and pod patching works.

In `@internal/common/tasks/scripts/build_image.sh`:
- Around line 222-225: The script prints user-supplied CUSTOM_DEFS_ARGS
(possible secrets) to logs; remove or redact those values in the echo/logging
statements. Specifically, in the build step guarded by BUILDER_CACHED (the block
with the echo "Running: aib build-builder ... ${CUSTOM_DEFS_ARGS[*]}
$LOCAL_BUILDER_IMAGE"), stop including CUSTOM_DEFS_ARGS in the echoed
command—either omit that array entirely or replace it with a safe placeholder
like "<REDACTED_DEFS>"—but keep the actual aib invocation using
"${CUSTOM_DEFS_ARGS[@]}". Ensure no other echo/log lines expose
CUSTOM_DEFS_ARGS.

---

Duplicate comments:
In `@internal/buildapi/progress.go`:
- Around line 82-97: The parseProgressAnnotation function currently accepts
negative numeric values; update it to validate parsed integers and reject
negatives by returning nil,false when either done or total is negative (i.e.,
after strconv.Atoi for parts[1] and parts[2] check if done < 0 || total < 0 and
return nil,false). Keep using the same function and BuildStep struct; no other
behavior changes.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 224cb1f and 638aef6.

📒 Files selected for processing (14)
  • cmd/caib/main.go
  • config/rbac/kustomization.yaml
  • config/rbac/pipeline_pod_role.yaml
  • config/rbac/pipeline_pod_rolebinding.yaml
  • internal/buildapi/client/client.go
  • internal/buildapi/openapi.yaml
  • internal/buildapi/progress.go
  • internal/buildapi/server.go
  • internal/common/tasks/scripts.go
  • internal/common/tasks/scripts/build_builder.sh
  • internal/common/tasks/scripts/build_image.sh
  • internal/common/tasks/scripts/common.sh
  • internal/common/tasks/scripts/flash_image.sh
  • internal/common/tasks/scripts/push_artifact.sh
🚧 Files skipped from review as they are similar to previous changes (5)
  • internal/buildapi/client/client.go
  • internal/common/tasks/scripts/common.sh
  • config/rbac/kustomization.yaml
  • internal/common/tasks/scripts.go
  • internal/buildapi/openapi.yaml

Comment thread config/rbac/pipeline_pod_role.yaml
Comment on lines +222 to +225
if [ "$BUILDER_CACHED" = "false" ]; then
echo "Builder image not found, building..."
echo "Running: aib build-builder --distro $(params.distro) --build-dir /_build --cache /_build/dnf-cache ${CUSTOM_DEFS_ARGS[*]} $LOCAL_BUILDER_IMAGE"
aib --verbose build-builder --build-dir /_build --cache /_build/dnf-cache --distro "$(params.distro)" "${CUSTOM_DEFS_ARGS[@]}" "$LOCAL_BUILDER_IMAGE"

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

Avoid logging custom definition values (may include secrets).

CUSTOM_DEFS_ARGS is sourced from user-provided definitions and can include credentials. Echoing the full array writes those values into task logs.

🔒 Suggested redaction
-    echo "Running: aib build-builder --distro $(params.distro) --build-dir /_build --cache /_build/dnf-cache ${CUSTOM_DEFS_ARGS[*]} $LOCAL_BUILDER_IMAGE"
+    if [ "${`#CUSTOM_DEFS_ARGS`[@]}" -gt 0 ]; then
+      echo "Running: aib build-builder --distro $(params.distro) --build-dir /_build --cache /_build/dnf-cache (custom defs: ${`#CUSTOM_DEFS_ARGS`[@]} provided) $LOCAL_BUILDER_IMAGE"
+    else
+      echo "Running: aib build-builder --distro $(params.distro) --build-dir /_build --cache /_build/dnf-cache $LOCAL_BUILDER_IMAGE"
+    fi
📝 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 [ "$BUILDER_CACHED" = "false" ]; then
echo "Builder image not found, building..."
echo "Running: aib build-builder --distro $(params.distro) --build-dir /_build --cache /_build/dnf-cache ${CUSTOM_DEFS_ARGS[*]} $LOCAL_BUILDER_IMAGE"
aib --verbose build-builder --build-dir /_build --cache /_build/dnf-cache --distro "$(params.distro)" "${CUSTOM_DEFS_ARGS[@]}" "$LOCAL_BUILDER_IMAGE"
if [ "$BUILDER_CACHED" = "false" ]; then
echo "Builder image not found, building..."
if [ "${`#CUSTOM_DEFS_ARGS`[@]}" -gt 0 ]; then
echo "Running: aib build-builder --distro $(params.distro) --build-dir /_build --cache /_build/dnf-cache (custom defs: ${`#CUSTOM_DEFS_ARGS`[@]} provided) $LOCAL_BUILDER_IMAGE"
else
echo "Running: aib build-builder --distro $(params.distro) --build-dir /_build --cache /_build/dnf-cache $LOCAL_BUILDER_IMAGE"
fi
aib --verbose build-builder --build-dir /_build --cache /_build/dnf-cache --distro "$(params.distro)" "${CUSTOM_DEFS_ARGS[@]}" "$LOCAL_BUILDER_IMAGE"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/common/tasks/scripts/build_image.sh` around lines 222 - 225, The
script prints user-supplied CUSTOM_DEFS_ARGS (possible secrets) to logs; remove
or redact those values in the echo/logging statements. Specifically, in the
build step guarded by BUILDER_CACHED (the block with the echo "Running: aib
build-builder ... ${CUSTOM_DEFS_ARGS[*]} $LOCAL_BUILDER_IMAGE"), stop including
CUSTOM_DEFS_ARGS in the echoed command—either omit that array entirely or
replace it with a safe placeholder like "<REDACTED_DEFS>"—but keep the actual
aib invocation using "${CUSTOM_DEFS_ARGS[@]}". Ensure no other echo/log lines
expose CUSTOM_DEFS_ARGS.

@bennyz bennyz merged commit f3c8d06 into centos-automotive-suite:main Feb 24, 2026
4 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Feb 25, 2026
@coderabbitai coderabbitai Bot mentioned this pull request Mar 22, 2026
@coderabbitai coderabbitai Bot mentioned this pull request Apr 27, 2026
10 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants