// HandlePod updates the state of the build based on the pod state func (bc *BuildPodController) HandlePod(pod *kapi.Pod) error { obj, exists, err := bc.BuildStore.Get(buildKey(pod)) if err != nil { glog.V(4).Infof("Error getting build for pod %s/%s: %v", pod.Namespace, pod.Name, err) return err } if !exists || obj == nil { glog.V(5).Infof("No build found for pod %s/%s", pod.Namespace, pod.Name) return nil } build := obj.(*buildapi.Build) nextStatus := build.Status.Phase switch pod.Status.Phase { case kapi.PodRunning: // The pod's still running nextStatus = buildapi.BuildPhaseRunning case kapi.PodSucceeded: // Check the exit codes of all the containers in the pod nextStatus = buildapi.BuildPhaseComplete if len(pod.Status.ContainerStatuses) == 0 { // no containers in the pod means something went badly wrong, so the build // should be failed. glog.V(2).Infof("Failing build %s/%s because the pod has no containers", build.Namespace, build.Name) nextStatus = buildapi.BuildPhaseFailed } else { for _, info := range pod.Status.ContainerStatuses { if info.State.Terminated != nil && info.State.Terminated.ExitCode != 0 { nextStatus = buildapi.BuildPhaseFailed break } } } case kapi.PodFailed: nextStatus = buildapi.BuildPhaseFailed } if build.Status.Phase != nextStatus && !buildutil.IsBuildComplete(build) { glog.V(4).Infof("Updating build %s/%s status %s -> %s", build.Namespace, build.Name, build.Status.Phase, nextStatus) build.Status.Phase = nextStatus build.Status.Reason = "" build.Status.Message = "" if buildutil.IsBuildComplete(build) { now := unversioned.Now() build.Status.CompletionTimestamp = &now } if build.Status.Phase == buildapi.BuildPhaseRunning { now := unversioned.Now() build.Status.StartTimestamp = &now } if err := bc.BuildUpdater.Update(build.Namespace, build); err != nil { return fmt.Errorf("failed to update build %s/%s: %v", build.Namespace, build.Name, err) } glog.V(4).Infof("Build %s/%s status was updated %s -> %s", build.Namespace, build.Name, build.Status.Phase, nextStatus) } return nil }
// HandleBuildPodDeletion sets the status of a build to error if the build pod has been deleted func (bc *BuildPodDeleteController) HandleBuildPodDeletion(pod *kapi.Pod) error { glog.V(4).Infof("Handling deletion of build pod %s/%s", pod.Namespace, pod.Name) obj, exists, err := bc.BuildStore.Get(buildKey(pod)) if err != nil { glog.V(4).Infof("Error getting build for pod %s/%s", pod.Namespace, pod.Name) return err } if !exists || obj == nil { glog.V(5).Infof("No Build found for deleted pod %s/%s", pod.Namespace, pod.Name) return nil } build := obj.(*buildapi.Build) if buildutil.IsBuildComplete(build) { glog.V(4).Infof("Pod was deleted but Build %s/%s is already completed, so no need to update it.", build.Namespace, build.Name) return nil } nextStatus := buildapi.BuildPhaseError if build.Status.Phase != nextStatus { glog.V(4).Infof("Updating build %s/%s status %s -> %s", build.Namespace, build.Name, build.Status.Phase, nextStatus) build.Status.Phase = nextStatus build.Status.Message = "The Pod for this Build was deleted before the Build completed." now := util.Now() build.Status.CompletionTimestamp = &now if err := bc.BuildUpdater.Update(build.Namespace, build); err != nil { return fmt.Errorf("Failed to update Build %s/%s: %v", build.Namespace, build.Name, err) } } return nil }
// HandlePod updates the state of the build based on the pod state func (bc *BuildPodController) HandlePod(pod *kapi.Pod) error { obj, exists, err := bc.BuildStore.Get(buildKey(pod)) if err != nil { glog.V(4).Infof("Error getting Build for pod %s/%s: %v", pod.Namespace, pod.Name, err) return err } if !exists || obj == nil { glog.V(5).Infof("No Build found for pod %s/%s", pod.Namespace, pod.Name) return nil } build := obj.(*buildapi.Build) // A cancelling event was triggered for the build, delete its pod and update build status. if build.Status.Cancelled { glog.V(4).Infof("Cancelling Build %s/%s.", build.Namespace, build.Name) if err := bc.CancelBuild(build, pod); err != nil { return fmt.Errorf("failed to cancel Build %s/%s: %v, will retry", build.Namespace, build.Name, err) } return nil } nextStatus := build.Status.Phase switch pod.Status.Phase { case kapi.PodRunning: // The pod's still running nextStatus = buildapi.BuildPhaseRunning case kapi.PodSucceeded, kapi.PodFailed: // Check the exit codes of all the containers in the pod nextStatus = buildapi.BuildPhaseComplete for _, info := range pod.Status.ContainerStatuses { if info.State.Terminated != nil && info.State.Terminated.ExitCode != 0 { nextStatus = buildapi.BuildPhaseFailed break } } } if build.Status.Phase != nextStatus { glog.V(4).Infof("Updating Build %s/%s status %s -> %s", build.Namespace, build.Name, build.Status.Phase, nextStatus) build.Status.Phase = nextStatus if buildutil.IsBuildComplete(build) { now := util.Now() build.Status.CompletionTimestamp = &now } if build.Status.Phase == buildapi.BuildPhaseRunning { now := util.Now() build.Status.StartTimestamp = &now } if err := bc.BuildUpdater.Update(build.Namespace, build); err != nil { return fmt.Errorf("failed to update Build %s/%s: %v", build.Namespace, build.Name, err) } glog.V(4).Infof("Build %s/%s status was updated %s -> %s", build.Namespace, build.Name, build.Status.Phase, nextStatus) } return nil }
// HandleBuild deletes pods for cancelled builds and takes new builds and puts // them in the pending state after creating a corresponding pod func (bc *BuildController) HandleBuild(build *buildapi.Build) error { // these builds are processed/updated/etc by the jenkins sync plugin if build.Spec.Strategy.JenkinsPipelineStrategy != nil { glog.V(4).Infof("Ignoring build with jenkins pipeline strategy") return nil } glog.V(4).Infof("Handling build %s/%s (%s)", build.Namespace, build.Name, build.Status.Phase) runPolicy := policy.ForBuild(build, bc.RunPolicies) if runPolicy == nil { return fmt.Errorf("unable to determine build scheduler for %s/%s", build.Namespace, build.Name) } if buildutil.IsBuildComplete(build) { if err := runPolicy.OnComplete(build); err != nil { return err } return nil } // A cancelling event was triggered for the build, delete its pod and update build status. if build.Status.Cancelled && build.Status.Phase != buildapi.BuildPhaseCancelled { glog.V(5).Infof("Marking build %s/%s as cancelled", build.Namespace, build.Name) if err := bc.CancelBuild(build); err != nil { build.Status.Reason = buildapi.StatusReasonCancelBuildFailed build.Status.Message = buildapi.StatusMessageCancelBuildFailed if err = bc.BuildUpdater.Update(build.Namespace, build); err != nil { utilruntime.HandleError(fmt.Errorf("Failed to update build %s/%s: %v", build.Namespace, build.Name, err)) } return fmt.Errorf("Failed to cancel build %s/%s: %v, will retry", build.Namespace, build.Name, err) } } // Handle only new builds from this point if build.Status.Phase != buildapi.BuildPhaseNew { return nil } // The runPolicy decides whether to execute this build or not. if run, err := runPolicy.IsRunnable(build); err != nil || !run { return err } if err := bc.nextBuildPhase(build); err != nil { return err } if err := bc.BuildUpdater.Update(build.Namespace, build); err != nil { // This is not a retryable error because the build has been created. The worst case // outcome of not updating the buildconfig is that we might rerun a build for the // same "new" imageid change in the future, which is better than guaranteeing we // run the build 2+ times by retrying it here. glog.V(2).Infof("Failed to record changes to build %s/%s: %v", build.Namespace, build.Name, err) } return nil }
func ValidateBuildUpdate(build *buildapi.Build, older *buildapi.Build) fielderrors.ValidationErrorList { allErrs := fielderrors.ValidationErrorList{} allErrs = append(allErrs, validation.ValidateObjectMetaUpdate(&build.ObjectMeta, &older.ObjectMeta).Prefix("metadata")...) allErrs = append(allErrs, ValidateBuild(build)...) if buildutil.IsBuildComplete(older) && older.Status.Phase != build.Status.Phase { allErrs = append(allErrs, fielderrors.NewFieldInvalid("status.Phase", build.Status.Phase, "phase cannot be updated from a terminal state")) } if !kapi.Semantic.DeepEqual(build.Spec, older.Spec) { allErrs = append(allErrs, fielderrors.NewFieldInvalid("spec", "content of spec is not printed out, please refer to the \"details\"", "spec is immutable")) } return allErrs }
// List lists all Pods associated with a Build. func (lw *buildPodDeleteLW) List(options kapi.ListOptions) (runtime.Object, error) { glog.V(5).Info("Checking for deleted build pods") buildList, err := lw.Client.Builds(kapi.NamespaceAll).List(options) if err != nil { glog.V(4).Infof("Failed to find any builds due to error %v", err) return nil, err } for _, build := range buildList.Items { glog.V(5).Infof("Found build %s/%s", build.Namespace, build.Name) if buildutil.IsBuildComplete(&build) { glog.V(5).Infof("Ignoring build %s/%s because it is complete", build.Namespace, build.Name) continue } if build.Spec.Strategy.JenkinsPipelineStrategy != nil { glog.V(5).Infof("Ignoring build %s/%s because it is a pipeline build", build.Namespace, build.Name) continue } pod, err := lw.KubeClient.Pods(build.Namespace).Get(buildapi.GetBuildPodName(&build)) if err != nil { if !kerrors.IsNotFound(err) { glog.V(4).Infof("Error getting pod for build %s/%s: %v", build.Namespace, build.Name, err) return nil, err } else { pod = nil } } else { if buildName := buildapi.GetBuildName(pod); buildName != build.Name { pod = nil } } if pod == nil { deletedPod := &kapi.Pod{ ObjectMeta: kapi.ObjectMeta{ Name: buildapi.GetBuildPodName(&build), Namespace: build.Namespace, }, } glog.V(4).Infof("No build pod found for build %s/%s, sending delete event for build pod", build.Namespace, build.Name) err := lw.store.Delete(deletedPod) if err != nil { glog.V(4).Infof("Error queuing delete event: %v", err) } } else { glog.V(5).Infof("Found build pod %s/%s for build %s", pod.Namespace, pod.Name, build.Name) } } return &kapi.PodList{}, nil }
// HandleBuildPodDeletion sets the status of a build to error if the build pod has been deleted func (bc *BuildPodDeleteController) HandleBuildPodDeletion(pod *kapi.Pod) error { glog.V(4).Infof("Handling deletion of build pod %s/%s", pod.Namespace, pod.Name) obj, exists, err := bc.BuildStore.Get(buildKey(pod)) if err != nil { glog.V(4).Infof("Error getting build for pod %s/%s", pod.Namespace, pod.Name) return err } if !exists || obj == nil { glog.V(5).Infof("No build found for deleted pod %s/%s", pod.Namespace, pod.Name) return nil } build := obj.(*buildapi.Build) if build.Spec.Strategy.JenkinsPipelineStrategy != nil { glog.V(4).Infof("Build %s/%s is a pipeline build, ignoring", build.Namespace, build.Name) return nil } // If build was cancelled, we'll leave HandleBuild to update the build if build.Status.Cancelled { glog.V(4).Infof("Cancelation for build %s/%s was already triggered, ignoring", build.Namespace, build.Name) return nil } if buildutil.IsBuildComplete(build) { glog.V(4).Infof("Pod was deleted but build %s/%s is already completed, so no need to update it.", build.Namespace, build.Name) return nil } nextStatus := buildapi.BuildPhaseError if build.Status.Phase != nextStatus { glog.V(4).Infof("Updating build %s/%s status %s -> %s", build.Namespace, build.Name, build.Status.Phase, nextStatus) build.Status.Phase = nextStatus build.Status.Reason = buildapi.StatusReasonBuildPodDeleted build.Status.Message = buildapi.StatusMessageBuildPodDeleted now := unversioned.Now() build.Status.CompletionTimestamp = &now if err := bc.BuildUpdater.Update(build.Namespace, build); err != nil { return fmt.Errorf("Failed to update build %s/%s: %v", build.Namespace, build.Name, err) } } return nil }
// cancelPreviousBuilds cancels all queued builds that have the build sequence number // lower than the given build. It retries the cancellation in case of conflict. func (s *SerialLatestOnlyPolicy) cancelPreviousBuilds(build *buildapi.Build) []error { bcName := buildutil.ConfigNameForBuild(build) if len(bcName) == 0 { return []error{} } currentBuildNumber, err := buildutil.BuildNumber(build) if err != nil { return []error{NewNoBuildNumberAnnotationError(build)} } builds, err := buildutil.BuildConfigBuilds(s.BuildLister, build.Namespace, bcName, func(b buildapi.Build) bool { // Do not cancel the complete builds, builds that were already cancelled, or // running builds. if buildutil.IsBuildComplete(&b) || b.Status.Phase == buildapi.BuildPhaseRunning { return false } // Prevent race-condition when there is a newer build than this and we don't // want to cancel it. The HandleBuild() function that runs for that build // will cancel this build. buildNumber, _ := buildutil.BuildNumber(&b) return buildNumber < currentBuildNumber }) if err != nil { return []error{err} } var result = []error{} for _, b := range builds.Items { err := wait.Poll(500*time.Millisecond, 5*time.Second, func() (bool, error) { b.Status.Cancelled = true err := s.BuildUpdater.Update(b.Namespace, &b) if err != nil && errors.IsConflict(err) { glog.V(5).Infof("Error cancelling build %s/%s: %v (will retry)", b.Namespace, b.Name, err) return false, nil } return true, err }) if err != nil { result = append(result, err) } } return result }
func ValidateBuildUpdate(build *buildapi.Build, older *buildapi.Build) field.ErrorList { allErrs := field.ErrorList{} allErrs = append(allErrs, validation.ValidateObjectMetaUpdate(&build.ObjectMeta, &older.ObjectMeta, field.NewPath("metadata"))...) allErrs = append(allErrs, ValidateBuild(build)...) if buildutil.IsBuildComplete(older) && older.Status.Phase != build.Status.Phase { allErrs = append(allErrs, field.Invalid(field.NewPath("status", "phase"), build.Status.Phase, "phase cannot be updated from a terminal state")) } if !kapi.Semantic.DeepEqual(build.Spec, older.Spec) { diff, err := diffBuildSpec(build.Spec, older.Spec) if err != nil { glog.V(2).Infof("Error calculating build spec patch: %v", err) diff = "[unknown]" } detail := fmt.Sprintf("spec is immutable, diff: %s", diff) allErrs = append(allErrs, field.Invalid(field.NewPath("spec"), "content of spec is not printed out, please refer to the details", detail)) } return allErrs }
// List lists all Pods associated with a Build. func (lw *buildPodDeleteLW) List() (runtime.Object, error) { glog.V(5).Info("Checking for deleted build pods") buildList, err := lw.Client.Builds(kapi.NamespaceAll).List(labels.Everything(), fields.Everything()) if err != nil { glog.V(4).Infof("Failed to find any builds due to error %v", err) return nil, err } for _, build := range buildList.Items { glog.V(5).Infof("Found build %s/%s", build.Namespace, build.Name) if buildutil.IsBuildComplete(&build) { glog.V(5).Infof("Ignoring build %s/%s because it is complete", build.Namespace, build.Name) continue } pod, err := lw.KubeClient.Pods(build.Namespace).Get(buildutil.GetBuildPodName(&build)) if err != nil && !kerrors.IsNotFound(err) { glog.V(4).Infof("Error getting pod for build %s/%s: %v", build.Namespace, build.Name, err) return nil, err } if (err != nil && kerrors.IsNotFound(err)) || pod.Labels[buildapi.BuildLabel] != build.Name { pod = nil } if pod == nil { deletedPod := &kapi.Pod{ ObjectMeta: kapi.ObjectMeta{ Name: buildutil.GetBuildPodName(&build), Namespace: build.Namespace, }, } glog.V(4).Infof("No build pod found for build %s/%s, sending delete event for build pod", build.Namespace, build.Name) err := lw.store.Delete(deletedPod) if err != nil { glog.V(4).Infof("Error queuing delete event: %v", err) } } else { glog.V(5).Infof("Found build pod %s/%s for build %s", pod.Namespace, pod.Name, build.Name) } } return &kapi.PodList{}, nil }
// ictMarker inspects the image change triggers for the provided deploymentconfig and returns // a marker in case of the following two scenarios: // // 1. The image stream pointed by the dc trigger doen not exist. // 2. The image stream tag pointed by the dc trigger does not exist and there is no build in // flight that could push to the tag. func ictMarker(g osgraph.Graph, f osgraph.Namer, dcNode *deploygraph.DeploymentConfigNode) *osgraph.Marker { for _, uncastIstNode := range g.PredecessorNodesByEdgeKind(dcNode, deployedges.TriggersDeploymentEdgeKind) { if istNode := uncastIstNode.(*imagegraph.ImageStreamTagNode); !istNode.Found() { // The image stream for the tag of interest does not exist. if isNode, exists := doesImageStreamExist(g, uncastIstNode); !exists { return &osgraph.Marker{ Node: dcNode, RelatedNodes: []graph.Node{uncastIstNode, isNode}, Severity: osgraph.ErrorSeverity, Key: MissingImageStreamErr, Message: fmt.Sprintf("The image trigger for %s will have no effect because %s does not exist.", f.ResourceName(dcNode), f.ResourceName(isNode)), // TODO: Suggest `oc create imagestream` once we have that. } } for _, bcNode := range buildedges.BuildConfigsForTag(g, istNode) { // Avoid warning for the dc image trigger in case there is a build in flight. if latestBuild := buildedges.GetLatestBuild(g, bcNode); latestBuild != nil && !buildutil.IsBuildComplete(latestBuild.Build) { return nil } } // The image stream tag of interest does not exist. return &osgraph.Marker{ Node: dcNode, RelatedNodes: []graph.Node{uncastIstNode}, Severity: osgraph.WarningSeverity, Key: MissingImageStreamTagWarning, Message: fmt.Sprintf("The image trigger for %s will have no effect until %s is imported or created by a build.", f.ResourceName(dcNode), f.ResourceName(istNode)), } } } return nil }
LabelSelector: buildutil.BuildConfigSelector(bcName), }) defer buildWatch.Stop() // Start first build stdout, _, err := exutil.StartBuild(oc, bcName, "-o=name") o.Expect(err).NotTo(o.HaveOccurred()) o.Expect(strings.TrimSpace(stdout)).ShouldNot(o.HaveLen(0)) // extract build name from "build/buildName" resource id startedBuilds = append(startedBuilds, strings.TrimSpace(strings.Split(stdout, "/")[1])) // Wait for it to become running for { event := <-buildWatch.ResultChan() build := event.Object.(*buildapi.Build) o.Expect(buildutil.IsBuildComplete(build)).Should(o.BeFalse()) if build.Name == startedBuilds[0] && build.Status.Phase == buildapi.BuildPhaseRunning { break } } for i := 0; i < 2; i++ { stdout, _, err = exutil.StartBuild(oc, bcName, "-o=name") o.Expect(err).NotTo(o.HaveOccurred()) o.Expect(strings.TrimSpace(stdout)).ShouldNot(o.HaveLen(0)) startedBuilds = append(startedBuilds, strings.TrimSpace(strings.Split(stdout, "/")[1])) } o.Expect(err).NotTo(o.HaveOccurred()) for {
// HandlePod updates the state of the build based on the pod state func (bc *BuildPodController) HandlePod(pod *kapi.Pod) error { obj, exists, err := bc.BuildStore.Get(buildKey(pod)) if err != nil { glog.V(4).Infof("Error getting build for pod %s/%s: %v", pod.Namespace, pod.Name, err) return err } if !exists || obj == nil { glog.V(5).Infof("No build found for pod %s/%s", pod.Namespace, pod.Name) return nil } build := obj.(*buildapi.Build) nextStatus := build.Status.Phase currentReason := build.Status.Reason switch pod.Status.Phase { case kapi.PodRunning: // The pod's still running build.Status.Reason = "" nextStatus = buildapi.BuildPhaseRunning case kapi.PodPending: build.Status.Reason = "" nextStatus = buildapi.BuildPhasePending if secret := build.Spec.Output.PushSecret; secret != nil && currentReason != buildapi.StatusReasonMissingPushSecret { if _, err := bc.SecretClient.Secrets(build.Namespace).Get(secret.Name); err != nil && errors.IsNotFound(err) { build.Status.Reason = buildapi.StatusReasonMissingPushSecret glog.V(4).Infof("Setting reason for pending build to %q due to missing secret %s/%s", build.Status.Reason, build.Namespace, secret.Name) } } case kapi.PodSucceeded: // Check the exit codes of all the containers in the pod nextStatus = buildapi.BuildPhaseComplete build.Status.Reason = "" if len(pod.Status.ContainerStatuses) == 0 { // no containers in the pod means something went badly wrong, so the build // should be failed. glog.V(2).Infof("Failing build %s/%s because the pod has no containers", build.Namespace, build.Name) nextStatus = buildapi.BuildPhaseFailed } else { for _, info := range pod.Status.ContainerStatuses { if info.State.Terminated != nil && info.State.Terminated.ExitCode != 0 { nextStatus = buildapi.BuildPhaseFailed break } } } case kapi.PodFailed: build.Status.Reason = "" nextStatus = buildapi.BuildPhaseFailed } // Update the build object when it progress to a next state or the reason for // the current state changed. if build.Status.Phase != nextStatus && !buildutil.IsBuildComplete(build) { reason := "" if len(build.Status.Reason) > 0 { reason = " (" + string(build.Status.Reason) + ")" } glog.V(4).Infof("Updating build %s/%s status %s -> %s%s", build.Namespace, build.Name, build.Status.Phase, nextStatus, reason) build.Status.Phase = nextStatus build.Status.Message = "" if buildutil.IsBuildComplete(build) { now := unversioned.Now() build.Status.CompletionTimestamp = &now } if build.Status.Phase == buildapi.BuildPhaseRunning { now := unversioned.Now() build.Status.StartTimestamp = &now } if err := bc.BuildUpdater.Update(build.Namespace, build); err != nil { return fmt.Errorf("failed to update build %s/%s: %v", build.Namespace, build.Name, err) } glog.V(4).Infof("Build %s/%s status was updated %s -> %s", build.Namespace, build.Name, build.Status.Phase, nextStatus) } return nil }
// RunCancelBuild implements all the necessary functionality for CancelBuild. func (o *CancelBuildOptions) RunCancelBuild() error { var builds []*buildapi.Build for _, name := range o.BuildNames { build, err := o.BuildClient.Get(name) if err != nil { o.ReportError(fmt.Errorf("build %s/%s not found", o.Namespace, name)) continue } stateMatch := false for _, state := range o.States { if strings.ToLower(string(build.Status.Phase)) == state { stateMatch = true break } } if stateMatch && !buildutil.IsBuildComplete(build) { builds = append(builds, build) } } if o.DumpLogs { for _, b := range builds { // Do not attempt to get logs from build that was not scheduled. if b.Status.Phase == buildapi.BuildPhaseNew { continue } opts := buildapi.BuildLogOptions{NoWait: true} response, err := o.Client.BuildLogs(o.Namespace).Get(b.Name, opts).Do().Raw() if err != nil { o.ReportError(fmt.Errorf("unable to fetch logs for %s/%s: %v", b.Namespace, b.Name, err)) continue } fmt.Fprintf(o.Out, "==== Build %s/%s logs ====\n", b.Namespace, b.Name) fmt.Fprint(o.Out, string(response)) } } var wg sync.WaitGroup for _, b := range builds { wg.Add(1) go func(build *buildapi.Build) { defer wg.Done() err := wait.Poll(500*time.Millisecond, 30*time.Second, func() (bool, error) { build.Status.Cancelled = true _, err := o.BuildClient.Update(build) switch { case err == nil: return true, nil case kapierrors.IsConflict(err): build, err = o.BuildClient.Get(build.Name) return false, err } return true, err }) if err != nil { o.ReportError(fmt.Errorf("build %s/%s failed to update: %v", build.Namespace, build.Name, err)) return } // Make sure the build phase is really cancelled. err = wait.Poll(500*time.Millisecond, 30*time.Second, func() (bool, error) { updatedBuild, err := o.BuildClient.Get(build.Name) if err != nil { return true, err } return updatedBuild.Status.Phase == buildapi.BuildPhaseCancelled, nil }) if err != nil { o.ReportError(fmt.Errorf("build %s/%s failed to cancel: %v", build.Namespace, build.Name, err)) return } resource, name, _ := cmdutil.ResolveResource(buildapi.Resource("builds"), build.Name, o.Mapper) kcmdutil.PrintSuccess(o.Mapper, false, o.Out, resource.Resource, name, "cancelled") }(b) } wg.Wait() if o.Restart { for _, b := range builds { request := &buildapi.BuildRequest{ObjectMeta: kapi.ObjectMeta{Name: b.Name}} build, err := o.BuildClient.Clone(request) if err != nil { o.ReportError(fmt.Errorf("build %s/%s failed to restart: %v", b.Namespace, b.Name, err)) continue } resource, name, _ := cmdutil.ResolveResource(buildapi.Resource("builds"), build.Name, o.Mapper) kcmdutil.PrintSuccess(o.Mapper, false, o.Out, resource.Resource, name, fmt.Sprintf("restarted build %q", b.Name)) } } if o.HasError { return errors.New("failure during the build cancellation") } return nil }