From e275de619af209fe231de6fa7ce17b788c109f0f Mon Sep 17 00:00:00 2001 From: Benny Zlotnik Date: Sat, 14 Mar 2026 14:49:25 +0200 Subject: [PATCH 1/2] show image details even if flashing fails So users can flash themselves Signed-off-by: Benny Zlotnik --- cmd/caib/buildcmd/logs.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/caib/buildcmd/logs.go b/cmd/caib/buildcmd/logs.go index 57fadb6c..d8f794df 100644 --- a/cmd/caib/buildcmd/logs.go +++ b/cmd/caib/buildcmd/logs.go @@ -162,6 +162,7 @@ func (h *Handler) waitForBuildCompletion(ctx context.Context, api *buildapiclien handleErr := fmt.Errorf("%s failed: %s", errPrefix, st.Message) if isFlashFailure { + h.displayBuildResults(ctx, api, name) h.handleFlashError(handleErr, st) } else { h.handleError(handleErr) From 155bf586d6cd1269d9a8144699f55b21a2f32d4f Mon Sep 17 00:00:00 2001 From: Benny Zlotnik Date: Sun, 15 Mar 2026 12:45:35 +0200 Subject: [PATCH 2/2] improve error message Signed-off-by: Benny Zlotnik Assisted-by: claude-sonnet-4.6 --- cmd/caib/buildcmd/build.go | 2 -- cmd/caib/buildcmd/flash_feedback.go | 11 ++------- cmd/caib/buildcmd/logs.go | 24 ++++---------------- internal/controller/imagebuild/controller.go | 22 +++++++++++++++++- 4 files changed, 27 insertions(+), 32 deletions(-) diff --git a/cmd/caib/buildcmd/build.go b/cmd/caib/buildcmd/build.go index 3924c16b..27440b8a 100644 --- a/cmd/caib/buildcmd/build.go +++ b/cmd/caib/buildcmd/build.go @@ -26,9 +26,7 @@ const ( phaseRunning = "Running" phaseUploading = "Uploading" - errPrefixBuild = "build" errPrefixFlash = "flash" - errPrefixPush = "push" ) // Options wires build handlers to caller-owned state and helper functions. diff --git a/cmd/caib/buildcmd/flash_feedback.go b/cmd/caib/buildcmd/flash_feedback.go index ac8e218a..9b1507f4 100644 --- a/cmd/caib/buildcmd/flash_feedback.go +++ b/cmd/caib/buildcmd/flash_feedback.go @@ -45,37 +45,30 @@ func (h *Handler) displayFlashInstructions(st *buildapitypes.BuildResponse, isFa } colorsSupported := h.supportsColorOutput() - var headerColor, commandColor, infoColor func(...any) string - var headerPrefix, commandPrefix string + var commandColor, infoColor func(...any) string + var commandPrefix string if isFailure { if colorsSupported { - headerColor = color.New(color.FgHiRed, color.Bold).SprintFunc() commandColor = color.New(color.FgHiYellow, color.Bold).SprintFunc() infoColor = color.New(color.FgHiWhite).SprintFunc() } else { - headerColor = func(a ...any) string { return fmt.Sprint(a...) } commandColor = func(a ...any) string { return fmt.Sprint(a...) } infoColor = func(a ...any) string { return fmt.Sprint(a...) } - headerPrefix = "[!] " commandPrefix = ">> " } } else { if colorsSupported { - headerColor = color.New(color.FgHiWhite, color.Bold).SprintFunc() commandColor = color.New(color.FgHiGreen, color.Bold).SprintFunc() infoColor = color.New(color.FgHiYellow).SprintFunc() } else { - headerColor = func(a ...any) string { return fmt.Sprint(a...) } commandColor = func(a ...any) string { return fmt.Sprint(a...) } infoColor = func(a ...any) string { return fmt.Sprint(a...) } - headerPrefix = "[*] " commandPrefix = ">> " } } if isFailure { - fmt.Printf("\n%s%s\n", headerPrefix, headerColor("Manual Flash Required")) fmt.Printf("%s\n", infoColor("Flash failed, but you can flash manually using Jumpstarter:")) } else { fmt.Printf("%s\n", infoColor("Jumpstarter is available for flashing:")) diff --git a/cmd/caib/buildcmd/logs.go b/cmd/caib/buildcmd/logs.go index d8f794df..c107f7f4 100644 --- a/cmd/caib/buildcmd/logs.go +++ b/cmd/caib/buildcmd/logs.go @@ -141,27 +141,11 @@ func (h *Handler) waitForBuildCompletion(ctx context.Context, api *buildapiclien } if st.Phase == phaseFailed { pb.Clear() - errPrefix := errPrefixBuild - isFlashFailure := false - - if strings.Contains(strings.ToLower(st.Message), errPrefixFlash) { - errPrefix = errPrefixFlash - isFlashFailure = true - } else if strings.Contains(strings.ToLower(st.Message), errPrefixPush) { - errPrefix = errPrefixPush - } else if lastPhase == phaseFlashing { - errPrefix = errPrefixFlash - isFlashFailure = true - } else if lastPhase == "Pushing" { - errPrefix = errPrefixPush - } else if *h.opts.FlashAfterBuild && - (lastPhase == phaseFlashing || strings.Contains(strings.ToLower(st.Message), errPrefixFlash)) { - errPrefix = errPrefixFlash - isFlashFailure = true - } + isFlashFailure := strings.Contains(strings.ToLower(st.Message), errPrefixFlash) || + lastPhase == phaseFlashing - handleErr := fmt.Errorf("%s failed: %s", errPrefix, st.Message) - if isFlashFailure { + handleErr := fmt.Errorf("%s", st.Message) + if isFlashFailure || *h.opts.FlashAfterBuild { h.displayBuildResults(ctx, api, name) h.handleFlashError(handleErr, st) } else { diff --git a/internal/controller/imagebuild/controller.go b/internal/controller/imagebuild/controller.go index 4e866e01..87c92553 100644 --- a/internal/controller/imagebuild/controller.go +++ b/internal/controller/imagebuild/controller.go @@ -374,7 +374,7 @@ func (r *ImageBuildReconciler) checkBuildProgress( // Build failed - cleanup transient secrets r.cleanupTransientSecrets(ctx, imageBuild, r.Log) - if err := r.updateStatus(ctx, imageBuild, phaseFailed, pipelineRunFailureMessage(pipelineRun)); err != nil { + if err := r.updateStatus(ctx, imageBuild, phaseFailed, r.pipelineRunFailureDetail(ctx, pipelineRun)); err != nil { log.Error(err, "Failed to update status to Failed") return ctrl.Result{}, err } @@ -1517,6 +1517,26 @@ func pipelineRunFailureMessage(pipelineRun *tektonv1.PipelineRun) string { return "Build failed" } +func (r *ImageBuildReconciler) pipelineRunFailureDetail(ctx context.Context, pipelineRun *tektonv1.PipelineRun) string { + for _, child := range pipelineRun.Status.ChildReferences { + if child.PipelineTaskName != "flash-image" { + continue + } + taskRun := &tektonv1.TaskRun{} + if err := r.Get(ctx, types.NamespacedName{ + Name: child.Name, + Namespace: pipelineRun.Namespace, + }, taskRun); err != nil { + break + } + if isTaskRunCompleted(taskRun) && !isTaskRunSuccessful(taskRun) { + return taskRunFailureMessage(taskRun, "Flash failed") + } + break + } + return pipelineRunFailureMessage(pipelineRun) +} + func taskRunFailureMessage(taskRun *tektonv1.TaskRun, fallback string) string { for _, condition := range taskRun.Status.Conditions { if condition.Type == conditionSucceeded && condition.Status != corev1.ConditionTrue && condition.Message != "" {