Skip to content

feat: add Shipwright in-cluster build support to setup-k8s.sh#148

Open
moonlight16 wants to merge 4 commits into
rossoctl:mainfrom
moonlight16:feat/shipwright-k8s-builds
Open

feat: add Shipwright in-cluster build support to setup-k8s.sh#148
moonlight16 wants to merge 4 commits into
rossoctl:mainfrom
moonlight16:feat/shipwright-k8s-builds

Conversation

@moonlight16

@moonlight16 moonlight16 commented Jul 22, 2026

Copy link
Copy Markdown
Member

Background

The Kind and OpenShift setup paths each have a way to get images built for the
cluster — setup-kind.sh builds locally and kind loads them, and setup-ocp.sh
can lean on OpenShift's built-in oc new-build. The generic Kubernetes path added in
#142 (setup-k8s.sh) consumes prebuilt image refs via --image/--sandbox-image,
since vanilla Kubernetes has no standard in-cluster build system to build them for you.

This PR adds an in-cluster build option for that path, using
Shipwright: a small driver script that builds the harness
(and optionally sandbox) image straight from a git branch to a registry the cluster
can already reach — no local Docker daemon and nothing pushed over the internet — and
prints the resulting image refs ready to hand to setup-k8s.sh. It's opt-in and
changes nothing for users who already build their images elsewhere.

What's included

  • deploy/knative/setup-shipwright-build.sh — driver script that creates a Shipwright
    Build + BuildRun, waits for it to complete, and prints HARNESS_IMAGE=/SANDBOX_IMAGE=
    lines that feed directly into setup-k8s.sh --image/--sandbox-image.
  • Templated Build manifests under deploy/knative/shipwright/ (build-harness.yaml,
    build-sandbox.yaml).
  • Updated deploy/knative/README-k8s.md with usage instructions and where this fits
    alongside the local-build path.
  • Fix (second commit): live-cluster testing surfaced a bug where the script hardcoded
    the Shipwright Build object's name (serverless-harness), so re-running it could
    silently overwrite an existing Build of the same name and clobber its config. Fixed by
    deriving the default Build name from --tag (e.g. serverless-harness-<tag>), adding
    --build-name to opt into targeting a specific existing Build on purpose, and warning
    before overwriting a Build whose spec differs from what the current run would set.
  • Fix (third commit): code review surfaced two more issues in that same collision guard:
    --build-name, when combined with --with-sandbox, produced identical harness/sandbox
    Build names instead of acting as a prefix; and the guard's existence check swallowed real
    kubectl errors as "no existing Build" and used unreliable substring matching. Fixed both.

Test plan

This is a draft PR — code review and live-cluster testing (including a re-verification
pass of the latest fix) are complete. Remaining before marking ready: the user's own final
review.

  • Tested live against a real Kubernetes cluster with Shipwright installed: built the
    harness image in-cluster via setup-shipwright-build.sh.
  • Verified the pushed image was pullable from the in-cluster registry.
  • Independent code review identified and fixed two collision-guard bugs (see third
    commit above).
  • Re-ran the collision-guard test live against the third commit's fix: confirmed
    --build-name now produces distinct harness/sandbox Build names, the guard warns on
    a genuine revision/image mismatch, does not false-positive on an idempotent re-run,
    and does not misreport a not-yet-created Build as an error. Production
    Build/serverless-harness and Build/serverless-harness-sandbox were unchanged
    throughout, and all test objects were cleaned up afterward.

Assisted-By: Claude (Anthropic AI) noreply@anthropic.com

Adds deploy/knative/setup-shipwright-build.sh plus templated Build manifests
so anyone on a Kubernetes cluster with Shipwright installed can build the
harness/sandbox images in-cluster and feed the resulting refs straight into
setup-k8s.sh --image/--sandbox-image, instead of needing a local Docker
daemon or pushing over the internet.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Jeremy Cohn <Jeremy.Cohn@ibm.com>
… name

Live-cluster testing showed that reusing the hardcoded "serverless-harness"
Build object name silently overwrote an existing Build's revision/output
image. Default the Build name to include --tag (serverless-harness-<tag>)
so a test run with a different tag can't collide with an existing Build,
add --build-name to target a specific Build on purpose, and warn before
overwriting a Build whose revision/output differs from what this run would
set.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Jeremy Cohn <Jeremy.Cohn@ibm.com>
--build-name combined with --with-sandbox produced identical harness/
sandbox Build names instead of acting as a prefix, contradicting its
own help text. Sandbox now gets "-sandbox" appended.

warn_if_build_exists_with_different_spec swallowed all kubectl errors
as "no existing Build" via `2>/dev/null || return 0`, and used grep
substring matching on raw JSON that produces false negatives on
plausible inputs (e.g. v2.1.0 vs 2.1.0). Now distinguishes real
kubectl errors from NotFound, and compares exact jsonpath-extracted
fields instead of substrings.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Jeremy Cohn <Jeremy.Cohn@ibm.com>
@moonlight16

Copy link
Copy Markdown
Member Author

Note on the failing trivy-scan check — this failure is unrelated to the changes in this PR.

  • This PR changes only deploy/knative/README-k8s.md, deploy/knative/setup-shipwright-build.sh, and two YAML manifests under deploy/knative/shipwright/. It touches no dependency manifests — every go.mod / go.sum / pnpm-lock.yaml / package.json is byte-identical to main.
  • trivy-scan runs trivy fs . with severity: CRITICAL,HIGH and exit-code: 1, so it fails on any HIGH/CRITICAL CVE in the existing dependency tree — regardless of what a PR changes.
  • The same job passed on July 21 (parent PR Add setup-k8s.sh: generic Kubernetes install path #142) and fails on July 22 (this PR), one day apart, after Trivy downloaded a freshly-updated vulnerability DB. This is DB drift surfacing a newly-disclosed CVE in an unchanged dependency, not a regression introduced here — the same scan would fail against current main.

Remediation (a dependency bump, .trivyignore, or ignore-unfixed: true) belongs in a separate maintenance PR, not this doc/script change.

@moonlight16
moonlight16 marked this pull request as ready for review July 22, 2026 18:06
@moonlight16
moonlight16 requested review from cwiklik and mrsabath July 22, 2026 18:06

@cwiklik cwiklik 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.

Adds an in-cluster Shipwright build path for the generic setup-k8s.sh flow: a driver script + two rendered Build CR templates + README. Nicely done — set -euo pipefail, stderr-only logging (keeps stdout clean for the HARNESS_IMAGE=/SANDBOX_IMAGE= output and --dry-run YAML), a careful warn-before-clobbering-an-existing-Build guard, and tag-derived Build names to avoid collisions. Passes shellcheck/hadolint/CodeQL; no secrets, no .claude/.vscode.

Three non-blocking notes, all on the build-failure path (see inline).

CI note: trivy-scan is failing, but it's the pre-existing dependency CVE shared across this repo's PRs — this PR adds no dependency manifests (only shell/YAML/docs), so it's not attributable to #148. That check will still gate merge; it needs an org-wide fix (dependency bump or a scoped .trivyignore), separate from this review.

LGTM.

Assisted-By: Claude Code

)"
log_info "Waiting for BuildRun/${buildrun} (timeout: ${WAIT_TIMEOUT})"
if ! "${KUBECTL[@]}" -n "$NAMESPACE" wait "buildrun/${buildrun}" \
--for=condition=Succeeded --timeout="$WAIT_TIMEOUT"; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (non-blocking): kubectl wait --for=condition=Succeeded doesn't short-circuit on failure. When a BuildRun fails, its Succeeded condition goes to False, and kubectl wait keeps blocking until --wait-timeout (default 20m) before it reports the failure. Consider also racing a --for=condition=Succeeded=false wait (kubectl ≥1.31) or polling .status.conditions, so a failed build surfaces promptly instead of hanging for the full timeout.

if ! "${KUBECTL[@]}" -n "$NAMESPACE" wait "buildrun/${buildrun}" \
--for=condition=Succeeded --timeout="$WAIT_TIMEOUT"; then
log_error "Build failed — logs:"
"${KUBECTL[@]}" -n "$NAMESPACE" logs "buildrun.shipwright.io/${buildrun}" --all-containers >&2 || true

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

nit: kubectl logs buildrun.shipwright.io/<name> won't return the build pod's logs — a BuildRun isn't a pod and has no logs subresource, so this best-effort (|| true) line likely prints nothing on failure. Fetching the underlying TaskRun/pod logs (or shp buildrun logs) would make the failure path actually actionable.

sed \
-e "s#__NAME__#${name}#g" \
-e "s#__NAMESPACE__#${NAMESPACE}#g" \
-e "s#__GIT_URL__#${GIT_URL}#g" \

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

nit: render_build uses sed with # as the delimiter while substituting user-supplied values (__GIT_URL__, __IMAGE__, …). A literal # in a git URL or image ref would break the substitution. Unlikely in practice, but envsubst or a delimiter guaranteed not to appear in the input would be more robust.

Addresses review nits on rossoctl#148:
- render_build used sed with '#' as the delimiter over user-supplied
  values; a '#' in a git URL/image ref (or '&'/'\' in a replacement)
  would corrupt the output. Switch to bash literal string replacement,
  which has no delimiter or metachar hazard.
- The final `$BUILD_SANDBOX && echo ...`, as the last statement under
  `set -e`, short-circuited to exit 1 on a successful harness-only build
  (no --with-sandbox). Use an explicit if so success reports exit 0.

Verified: shellcheck clean; dry-run renders a '#'-containing revision
intact and exits 0.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Jeremy Cohn <Jeremy.Cohn@ibm.com>
@moonlight16

Copy link
Copy Markdown
Member Author

Addressed the non-blocking review nits (thanks @cwiklik):

  • render_build no longer uses sed over user-supplied values — switched to bash literal string replacement, so a # in a git URL/image ref (or &/\ in a replacement) can't corrupt the render. Verified a #-containing revision renders intact.
  • Also fixed a latent bug surfaced in live use: the final $BUILD_SANDBOX && echo short-circuited to exit 1 on a successful harness-only build under set -e; now an explicit if.

Deferred the two build-failure-path suggestions (short-circuit on BuildRun failure; fetch TaskRun/pod logs) as follow-ups — they're behavior changes worth their own change. shellcheck clean.

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