From d6a25eb6f60665f12c33d51addd7f6a5b226d1af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Barcarol=20Guimar=C3=A3es?= Date: Tue, 9 Aug 2022 11:01:11 +0000 Subject: [PATCH 1/6] pkg/steps: vendoring is no longer hard --- pkg/steps/source.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/steps/source.go b/pkg/steps/source.go index 0cd86447d32..82085954df0 100644 --- a/pkg/steps/source.go +++ b/pkg/steps/source.go @@ -499,7 +499,7 @@ func waitForBuildDeletion(ctx context.Context, client ctrlruntimeclient.Client, func isInfraReason(reason buildapi.StatusReason) bool { infraReasons := []buildapi.StatusReason{ - buildapi.StatusReason("BuildPodEvicted"), // vendoring to get this is so hard + buildapi.StatusReasonBuildPodEvicted, buildapi.StatusReasonBuildPodDeleted, buildapi.StatusReasonBuildPodExists, buildapi.StatusReasonCannotCreateBuildPod, From 307dcb8d320a1efa4f2b6a1c170fe2d27d320803 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Barcarol=20Guimar=C3=A3es?= Date: Fri, 21 Apr 2023 14:08:58 +0000 Subject: [PATCH 2/6] pkg/steps: remove build timeout This parameter is only ever set in tests. --- pkg/steps/source.go | 6 +++--- pkg/steps/source_test.go | 7 +------ 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/pkg/steps/source.go b/pkg/steps/source.go index 82085954df0..8557b9b02b7 100644 --- a/pkg/steps/source.go +++ b/pkg/steps/source.go @@ -533,10 +533,10 @@ func hintsAtInfraReason(logSnippet string) bool { } func waitForBuildOrTimeout(ctx context.Context, buildClient BuildClient, namespace, name string) error { - return waitForBuild(ctx, buildClient, namespace, name, 0, buildDuration) + return waitForBuild(ctx, buildClient, namespace, name, buildDuration) } -func waitForBuild(ctx context.Context, buildClient BuildClient, namespace, name string, timeout time.Duration, buildDurationFunc func(*buildapi.Build) time.Duration) error { +func waitForBuild(ctx context.Context, buildClient BuildClient, namespace, name string, buildDurationFunc func(*buildapi.Build) time.Duration) error { isOK := func(b *buildapi.Build) bool { return b.Status.Phase == buildapi.BuildPhaseComplete } @@ -569,7 +569,7 @@ func waitForBuild(ctx context.Context, buildClient BuildClient, namespace, name return false, nil } - return kubernetes.WaitForConditionOnObject(ctx, buildClient, ctrlruntimeclient.ObjectKey{Namespace: namespace, Name: name}, &buildapi.BuildList{}, &buildapi.Build{}, evaluatorFunc, timeout) + return kubernetes.WaitForConditionOnObject(ctx, buildClient, ctrlruntimeclient.ObjectKey{Namespace: namespace, Name: name}, &buildapi.BuildList{}, &buildapi.Build{}, evaluatorFunc, 0) } func buildDuration(build *buildapi.Build) time.Duration { diff --git a/pkg/steps/source_test.go b/pkg/steps/source_test.go index ed439f81a3e..8020e241464 100644 --- a/pkg/steps/source_test.go +++ b/pkg/steps/source_test.go @@ -391,11 +391,6 @@ func TestWaitForBuild(t *testing.T) { buildClient BuildClient expected error }{ - { - name: "timeout", - buildClient: NewBuildClient(loggingclient.New(fakectrlruntimeclient.NewClientBuilder().WithRuntimeObjects().Build()), nil, nil), - expected: fmt.Errorf("timed out waiting for the condition"), - }, { name: "build succeeded", buildClient: NewBuildClient(loggingclient.New(fakectrlruntimeclient.NewClientBuilder().WithRuntimeObjects( @@ -430,7 +425,7 @@ func TestWaitForBuild(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { - actual := waitForBuild(context.TODO(), testCase.buildClient, "some-ns", "some-build", 90*time.Millisecond, func(build *buildapi.Build) time.Duration { + actual := waitForBuild(context.TODO(), testCase.buildClient, "some-ns", "some-build", func(build *buildapi.Build) time.Duration { return 3 * time.Second }) if diff := cmp.Diff(testCase.expected, actual, testhelper.EquateErrorMessage); diff != "" { From 9a44968f64494d43228d6051a5072751e1133d6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Barcarol=20Guimar=C3=A3es?= Date: Thu, 11 Aug 2022 14:05:14 +0000 Subject: [PATCH 3/6] pkg/steps: simplify code --- pkg/steps/source.go | 77 ++++++++++++++++++---------------------- pkg/steps/source_test.go | 20 ++++++----- 2 files changed, 46 insertions(+), 51 deletions(-) diff --git a/pkg/steps/source.go b/pkg/steps/source.go index 8557b9b02b7..bdc52da040f 100644 --- a/pkg/steps/source.go +++ b/pkg/steps/source.go @@ -17,7 +17,6 @@ import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/errors" utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/wait" prowv1 "k8s.io/test-infra/prow/apis/prowjobs/v1" @@ -417,40 +416,41 @@ func constructMultiArchBuilds(build buildapi.Build, nodeArchitectures []string) return ret } -func handleBuild(ctx context.Context, buildClient BuildClient, build buildapi.Build) error { - var buildErrs []error - attempts := 5 - if boErr := wait.ExponentialBackoff(wait.Backoff{Duration: time.Minute, Factor: 1.5, Steps: attempts}, func() (bool, error) { - var buildAttempt buildapi.Build - build.DeepCopyInto(&buildAttempt) - if err := buildClient.Create(ctx, &buildAttempt); err != nil && !kerrors.IsAlreadyExists(err) { - return false, fmt.Errorf("could not create build %s: %w", buildAttempt.Name, err) +func handleBuild(ctx context.Context, client BuildClient, build buildapi.Build) error { + const attempts = 5 + ns, name := build.Namespace, build.Name + var errs []error + if err := wait.ExponentialBackoff(wait.Backoff{Duration: time.Minute, Factor: 1.5, Steps: attempts}, func() (bool, error) { + var attempt buildapi.Build + build.DeepCopyInto(&attempt) + if err := client.Create(ctx, &attempt); err != nil && !kerrors.IsAlreadyExists(err) { + return false, fmt.Errorf("could not create build %s: %w", name, err) } - buildErr := waitForBuildOrTimeout(ctx, buildClient, buildAttempt.Namespace, buildAttempt.Name) - if buildErr == nil { - if err := gatherSuccessfulBuildLog(buildClient, buildAttempt.Namespace, buildAttempt.Name); err != nil { + err := waitForBuildOrTimeout(ctx, client, ns, name) + if err == nil { + if err := gatherSuccessfulBuildLog(client, ns, name); err != nil { // log error but do not fail successful build - logrus.WithError(err).Warnf("Failed gathering successful build %s logs into artifacts.", buildAttempt.Name) + logrus.WithError(err).Warnf("Failed gathering successful build %s logs into artifacts.", name) } return true, nil } - buildErrs = append(buildErrs, buildErr) + errs = append(errs, err) b := &buildapi.Build{} - if err := buildClient.Get(ctx, ctrlruntimeclient.ObjectKey{Namespace: buildAttempt.Namespace, Name: buildAttempt.Name}, b); err != nil { - return false, fmt.Errorf("could not get build %s: %w", buildAttempt.Name, err) + if err := client.Get(ctx, ctrlruntimeclient.ObjectKey{Namespace: ns, Name: name}, b); err != nil { + return false, fmt.Errorf("could not get build %s: %w", name, err) } if !isBuildPhaseTerminated(b.Status.Phase) { - return false, buildErr + return false, err } if !(isInfraReason(b.Status.Reason) || hintsAtInfraReason(b.Status.LogSnippet)) { - return false, buildErr + return false, err } - logrus.Infof("Build %s previously failed from an infrastructure error (%s), retrying...", b.Name, b.Status.Reason) + logrus.Infof("Build %s previously failed from an infrastructure error (%s), retrying...", name, b.Status.Reason) zero := int64(0) foreground := metav1.DeletePropagationForeground opts := metav1.DeleteOptions{ @@ -458,18 +458,18 @@ func handleBuild(ctx context.Context, buildClient BuildClient, build buildapi.Bu Preconditions: &metav1.Preconditions{UID: &b.UID}, PropagationPolicy: &foreground, } - if err := buildClient.Delete(ctx, &buildAttempt, &ctrlruntimeclient.DeleteOptions{Raw: &opts}); err != nil && !kerrors.IsNotFound(err) && !kerrors.IsConflict(err) { - return false, fmt.Errorf("could not delete build %s: %w", buildAttempt.Name, err) + if err := client.Delete(ctx, &attempt, &ctrlruntimeclient.DeleteOptions{Raw: &opts}); err != nil && !kerrors.IsNotFound(err) && !kerrors.IsConflict(err) { + return false, fmt.Errorf("could not delete build %s: %w", name, err) } - if err := waitForBuildDeletion(ctx, buildClient, buildAttempt.Namespace, buildAttempt.Name); err != nil { - return false, fmt.Errorf("could not wait for build %s to be deleted: %w", buildAttempt.Name, err) + if err := waitForBuildDeletion(ctx, client, ns, name); err != nil { + return false, fmt.Errorf("could not wait for build %s to be deleted: %w", name, err) } return false, nil - }); boErr != nil { - if boErr == wait.ErrWaitTimeout { - return fmt.Errorf("build not successful after %d attempts: %w", attempts, errors.NewAggregate(buildErrs)) + }); err != nil { + if err == wait.ErrWaitTimeout { + return fmt.Errorf("build not successful after %d attempts: %w", attempts, utilerrors.NewAggregate(errs)) } - return boErr + return err } return nil } @@ -533,19 +533,10 @@ func hintsAtInfraReason(logSnippet string) bool { } func waitForBuildOrTimeout(ctx context.Context, buildClient BuildClient, namespace, name string) error { - return waitForBuild(ctx, buildClient, namespace, name, buildDuration) + return waitForBuild(ctx, buildClient, namespace, name) } -func waitForBuild(ctx context.Context, buildClient BuildClient, namespace, name string, buildDurationFunc func(*buildapi.Build) time.Duration) error { - isOK := func(b *buildapi.Build) bool { - return b.Status.Phase == buildapi.BuildPhaseComplete - } - isFailed := func(b *buildapi.Build) bool { - return b.Status.Phase == buildapi.BuildPhaseFailed || - b.Status.Phase == buildapi.BuildPhaseCancelled || - b.Status.Phase == buildapi.BuildPhaseError - } - +func waitForBuild(ctx context.Context, buildClient BuildClient, namespace, name string) error { logrus.WithFields(logrus.Fields{ "namespace": namespace, "name": name, @@ -554,14 +545,14 @@ func waitForBuild(ctx context.Context, buildClient BuildClient, namespace, name evaluatorFunc := func(obj runtime.Object) (bool, error) { switch build := obj.(type) { case *buildapi.Build: - if isOK(build) { - logrus.Infof("Build %s succeeded after %s", build.Name, buildDurationFunc(build).Truncate(time.Second)) + switch build.Status.Phase { + case buildapi.BuildPhaseComplete: + logrus.Infof("Build %s succeeded after %s", build.Name, buildDuration(build).Truncate(time.Second)) return true, nil - } - if isFailed(build) { + case buildapi.BuildPhaseFailed, buildapi.BuildPhaseCancelled, buildapi.BuildPhaseError: logrus.Infof("Build %s failed, printing logs:", build.Name) printBuildLogs(buildClient, build.Namespace, build.Name) - return true, util.AppendLogToError(fmt.Errorf("the build %s failed after %s with reason %s: %s", build.Name, buildDurationFunc(build).Truncate(time.Second), build.Status.Reason, build.Status.Message), build.Status.LogSnippet) + return true, util.AppendLogToError(fmt.Errorf("the build %s failed after %s with reason %s: %s", build.Name, buildDuration(build).Truncate(time.Second), build.Status.Reason, build.Status.Message), build.Status.LogSnippet) } default: return false, fmt.Errorf("build/%v ns/%v got an event that did not contain a build: %v", name, namespace, obj) diff --git a/pkg/steps/source_test.go b/pkg/steps/source_test.go index 8020e241464..7b875064d49 100644 --- a/pkg/steps/source_test.go +++ b/pkg/steps/source_test.go @@ -386,6 +386,8 @@ func init() { } func TestWaitForBuild(t *testing.T) { + now := meta.Time{Time: time.Now()} + start, end := meta.Time{Time: now.Time.Add(-3 * time.Second)}, now var testCases = []struct { name string buildClient BuildClient @@ -400,7 +402,9 @@ func TestWaitForBuild(t *testing.T) { Namespace: "some-ns", }, Status: buildapi.BuildStatus{ - Phase: buildapi.BuildPhaseComplete, + Phase: buildapi.BuildPhaseComplete, + StartTimestamp: &start, + CompletionTimestamp: &end, }, }).Build()), nil, nil), }, @@ -413,10 +417,12 @@ func TestWaitForBuild(t *testing.T) { Namespace: "some-ns", }, Status: buildapi.BuildStatus{ - Phase: buildapi.BuildPhaseCancelled, - Reason: "reason", - Message: "msg", - LogSnippet: "snippet", + Phase: buildapi.BuildPhaseCancelled, + Reason: "reason", + Message: "msg", + LogSnippet: "snippet", + StartTimestamp: &start, + CompletionTimestamp: &end, }, }).Build()), "abc\n"), // the line break is for gotestsum https://github.com/gotestyourself/gotestsum/issues/141#issuecomment-1209146526 expected: fmt.Errorf("%s\n\n%s", "the build some-build failed after 3s with reason reason: msg", "snippet"), @@ -425,9 +431,7 @@ func TestWaitForBuild(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { - actual := waitForBuild(context.TODO(), testCase.buildClient, "some-ns", "some-build", func(build *buildapi.Build) time.Duration { - return 3 * time.Second - }) + actual := waitForBuild(context.TODO(), testCase.buildClient, "some-ns", "some-build") if diff := cmp.Diff(testCase.expected, actual, testhelper.EquateErrorMessage); diff != "" { t.Errorf("%s: mismatch (-expected +actual), diff: %s", testCase.name, diff) } From 3843be9d9624edb17bce4f2e63572e343c0dd7ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Barcarol=20Guimar=C3=A3es?= Date: Wed, 19 Apr 2023 12:05:15 +0000 Subject: [PATCH 4/6] pkg/steps: extract function --- pkg/steps/source.go | 61 ++++++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/pkg/steps/source.go b/pkg/steps/source.go index bdc52da040f..e10045b7282 100644 --- a/pkg/steps/source.go +++ b/pkg/steps/source.go @@ -360,6 +360,37 @@ func buildInputsFromStep(inputs map[string]api.ImageBuildInputs) []buildapi.Imag return refs } +func handleFailedBuild(ctx context.Context, client BuildClient, ns, name string, err error) error { + b := &buildapi.Build{} + if err := client.Get(ctx, ctrlruntimeclient.ObjectKey{Namespace: ns, Name: name}, b); err != nil { + return fmt.Errorf("could not get build %s: %w", name, err) + } + + if !isBuildPhaseTerminated(b.Status.Phase) { + return err + } + + if !(isInfraReason(b.Status.Reason) || hintsAtInfraReason(b.Status.LogSnippet)) { + return err + } + + logrus.Infof("Build %s previously failed from an infrastructure error (%s), retrying...", name, b.Status.Reason) + zero := int64(0) + foreground := metav1.DeletePropagationForeground + opts := metav1.DeleteOptions{ + GracePeriodSeconds: &zero, + Preconditions: &metav1.Preconditions{UID: &b.UID}, + PropagationPolicy: &foreground, + } + if err := client.Delete(ctx, b, &ctrlruntimeclient.DeleteOptions{Raw: &opts}); err != nil && !kerrors.IsNotFound(err) && !kerrors.IsConflict(err) { + return fmt.Errorf("could not delete build %s: %w", name, err) + } + if err := waitForBuildDeletion(ctx, client, ns, name); err != nil { + return fmt.Errorf("could not wait for build %s to be deleted: %w", name, err) + } + return nil +} + func isBuildPhaseTerminated(phase buildapi.BuildPhase) bool { switch phase { case buildapi.BuildPhaseNew, @@ -436,35 +467,7 @@ func handleBuild(ctx context.Context, client BuildClient, build buildapi.Build) return true, nil } errs = append(errs, err) - - b := &buildapi.Build{} - if err := client.Get(ctx, ctrlruntimeclient.ObjectKey{Namespace: ns, Name: name}, b); err != nil { - return false, fmt.Errorf("could not get build %s: %w", name, err) - } - - if !isBuildPhaseTerminated(b.Status.Phase) { - return false, err - } - - if !(isInfraReason(b.Status.Reason) || hintsAtInfraReason(b.Status.LogSnippet)) { - return false, err - } - - logrus.Infof("Build %s previously failed from an infrastructure error (%s), retrying...", name, b.Status.Reason) - zero := int64(0) - foreground := metav1.DeletePropagationForeground - opts := metav1.DeleteOptions{ - GracePeriodSeconds: &zero, - Preconditions: &metav1.Preconditions{UID: &b.UID}, - PropagationPolicy: &foreground, - } - if err := client.Delete(ctx, &attempt, &ctrlruntimeclient.DeleteOptions{Raw: &opts}); err != nil && !kerrors.IsNotFound(err) && !kerrors.IsConflict(err) { - return false, fmt.Errorf("could not delete build %s: %w", name, err) - } - if err := waitForBuildDeletion(ctx, client, ns, name); err != nil { - return false, fmt.Errorf("could not wait for build %s to be deleted: %w", name, err) - } - return false, nil + return false, handleFailedBuild(ctx, client, ns, name, err) }); err != nil { if err == wait.ErrWaitTimeout { return fmt.Errorf("build not successful after %d attempts: %w", attempts, utilerrors.NewAggregate(errs)) From 9d5ed60b9c5ccd55e0609bb979c60e3212e14e20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Barcarol=20Guimar=C3=A3es?= Date: Wed, 19 Apr 2023 12:11:33 +0000 Subject: [PATCH 5/6] pkg/steps: invert error handling order --- pkg/steps/source.go | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/pkg/steps/source.go b/pkg/steps/source.go index e10045b7282..737b0edc831 100644 --- a/pkg/steps/source.go +++ b/pkg/steps/source.go @@ -457,17 +457,15 @@ func handleBuild(ctx context.Context, client BuildClient, build buildapi.Build) if err := client.Create(ctx, &attempt); err != nil && !kerrors.IsAlreadyExists(err) { return false, fmt.Errorf("could not create build %s: %w", name, err) } - - err := waitForBuildOrTimeout(ctx, client, ns, name) - if err == nil { - if err := gatherSuccessfulBuildLog(client, ns, name); err != nil { - // log error but do not fail successful build - logrus.WithError(err).Warnf("Failed gathering successful build %s logs into artifacts.", name) - } - return true, nil + if err := waitForBuildOrTimeout(ctx, client, ns, name); err != nil { + errs = append(errs, err) + return false, handleFailedBuild(ctx, client, ns, name, err) } - errs = append(errs, err) - return false, handleFailedBuild(ctx, client, ns, name, err) + if err := gatherSuccessfulBuildLog(client, ns, name); err != nil { + // log error but do not fail successful build + logrus.WithError(err).Warnf("Failed gathering successful build %s logs into artifacts.", name) + } + return true, nil }); err != nil { if err == wait.ErrWaitTimeout { return fmt.Errorf("build not successful after %d attempts: %w", attempts, utilerrors.NewAggregate(errs)) From 411ddc23a6f02c6a32fd69939a3d227a3beafb43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Barcarol=20Guimar=C3=A3es?= Date: Wed, 10 Aug 2022 15:48:03 +0000 Subject: [PATCH 6/6] pkg/steps: emit detailed build errors in debug log MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It can be difficult to identify when builds are reused based on the main output: ``` … INFO[2023-04-20T13:18:59Z] Building failure INFO[2023-04-20T13:19:31Z] Build failure failed, printing logs: … STEP 3/5: RUN false error: build error: error building at STEP "RUN false": error while running runtime: exit status 1 INFO[2023-04-20T13:19:32Z] Ran for 44s ERRO[2023-04-20T13:19:32Z] Some steps failed: ERRO[2023-04-20T13:19:32Z] * could not run steps: step failure failed: error occurred handling build failure: the build failure failed after 32s with reason DockerBuildFailed: Dockerfile build strategy has failed. … ``` Other than a few accidental log messages from the build environment, there is no temporal information in the build logs. This is especially problematic for permanent failures which will never succeed without the deletion of the build, either directly or via the deletion of the test namespace. Identifying this type of scenario requires searching the artifacts for the generated `Build` objects, which is laborious at best but can also be impossible in the infamous case of the reuse of a build scheduled on a node which has since been removed from the cluster. To make it easier to identify these cases, more information is now emitted: - a message in the main log informs whether builds are created or reused - a message in the debug log details failure conditions ``` INFO[2023-04-20T13:23:57Z] Building failure INFO[2023-04-20T13:23:57Z] Found existing build "failure" INFO[2023-04-20T13:23:57Z] Build failure failed, printing logs: ``` ``` $ jq --raw-output '[.time,.msg]|join(" ")' ci-operator.log 2023-04-20T13:23:50Z Building failure … 2023-04-20T13:23:57Z Building failure 2023-04-20T13:23:57Z Found existing build "failure" 2023-04-20T13:23:57Z Waiting for build to be complete. 2023-04-20T13:23:57Z Build failure failed, printing logs: 2023-04-20T13:23:58Z Build "failure" (created at 2023-04-20 13:20:45 +0000 UTC) classified as legitimate failure, will not be retried … ``` For nonexistent pods: ``` $ ci-operator … … INFO[2023-04-20T13:26:54Z] Building failure INFO[2023-04-20T13:26:55Z] Found existing build "failure" INFO[2023-04-20T13:26:55Z] Build failure failed, printing logs: WARN[2023-04-20T13:26:55Z] Unable to retrieve logs from failed build error=pod "failure-build" not found … ``` ``` $ jq … ci-operator.log 2022-08-15T10:21:58Z unset version 0 … 2023-04-20T13:26:54Z Building failure 2023-04-20T13:26:55Z Found existing build "failure" 2023-04-20T13:26:55Z Waiting for build to be complete. 2023-04-20T13:26:55Z Build failure failed, printing logs: 2023-04-20T13:26:55Z Unable to retrieve logs from failed build 2023-04-20T13:26:55Z Build "failure" (created at 2023-04-20 13:20:45 +0000 UTC) classified as legitimate failure, will not be retried … ``` --- pkg/steps/source.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pkg/steps/source.go b/pkg/steps/source.go index 737b0edc831..d524f15d71d 100644 --- a/pkg/steps/source.go +++ b/pkg/steps/source.go @@ -367,10 +367,12 @@ func handleFailedBuild(ctx context.Context, client BuildClient, ns, name string, } if !isBuildPhaseTerminated(b.Status.Phase) { + logrus.Debugf("Build %q (created at %v) still in phase %q", name, b.CreationTimestamp, b.Status.Phase) return err } if !(isInfraReason(b.Status.Reason) || hintsAtInfraReason(b.Status.LogSnippet)) { + logrus.Debugf("Build %q (created at %v) classified as legitimate failure, will not be retried", name, b.CreationTimestamp) return err } @@ -454,7 +456,11 @@ func handleBuild(ctx context.Context, client BuildClient, build buildapi.Build) if err := wait.ExponentialBackoff(wait.Backoff{Duration: time.Minute, Factor: 1.5, Steps: attempts}, func() (bool, error) { var attempt buildapi.Build build.DeepCopyInto(&attempt) - if err := client.Create(ctx, &attempt); err != nil && !kerrors.IsAlreadyExists(err) { + if err := client.Create(ctx, &attempt); err == nil { + logrus.Infof("Created build %q", name) + } else if kerrors.IsAlreadyExists(err) { + logrus.Infof("Found existing build %q", name) + } else { return false, fmt.Errorf("could not create build %s: %w", name, err) } if err := waitForBuildOrTimeout(ctx, client, ns, name); err != nil {