package main import ( "context" "slices" "strings" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) func (c *controller) recoverBuilds(ctx context.Context, delivery *projectDelivery) (bool, error) { known := make(map[string]bool) for _, attempt := range delivery.Status.Points { known[attempt.WorkflowName] = true } continuous := delivery.Status.Continuous if continuous != nil && continuous.Attempt != nil { known[continuous.Attempt.WorkflowName] = true if continuous.Attempt.History != nil { for _, name := range continuous.Attempt.History.Workflows { known[name] = true } } } items, err := c.client.Resource(workflowGVR).Namespace(delivery.Metadata.Namespace).List(ctx, metav1.ListOptions{LabelSelector: purposeAnnotation + "=build"}) if err != nil { return false, err } slices.SortFunc(items.Items, func(a, b unstructured.Unstructured) int { left, right := a.GetCreationTimestamp(), b.GetCreationTimestamp() if order := left.Time.Compare(right.Time); order != 0 { return order } return strings.Compare(a.GetName(), b.GetName()) }) recovered := false if continuous != nil && continuous.Attempt != nil && continuous.Attempt.CreatedAt == nil { for _, item := range items.Items { if item.GetName() == continuous.Attempt.WorkflowName && ownedBy(&item, delivery.Metadata.UID) { created := item.GetCreationTimestamp() continuous.Attempt.CreatedAt = &created.Time recovered = true break } } } for _, item := range items.Items { knownBuild := known[item.GetName()] if knownBuild { if continuous == nil || continuous.Attempt == nil || continuous.Attempt.History == nil || !slices.Contains(continuous.Attempt.History.Workflows, item.GetName()) || continuous.Attempt.CreatedAt == nil { continue } created := item.GetCreationTimestamp() if !created.Time.After(*continuous.Attempt.CreatedAt) { continue } } if !ownedBy(&item, delivery.Metadata.UID) { continue } state, err := c.workflow(ctx, delivery.Metadata.Namespace, item.GetName()) if err != nil { return false, err } attempt := &attemptStatus{ WorkflowName: item.GetName(), Ref: state.Input["source-ref"], Revision: state.Input["source-revision"], SourceIdentity: item.GetAnnotations()[sourceKey], RetryOf: item.GetAnnotations()[retryKey], Phase: phaseOrPending(state.Phase), Input: state.Input, CreatedAt: state.CreatedAt, } if knownBuild && attempt.SourceIdentity == "" && continuous.Delivered != nil && continuous.Delivered.Revision == attempt.Revision { attempt.SourceIdentity = continuous.Delivered.Identity } if result, found := state.Outputs["delivery-result"]; found { attempt.Result = opaqueResult(result) } if attempt.Ref == "" { if slices.ContainsFunc(delivery.Status.Points, func(point attemptStatus) bool { return !terminalPhase(point.Phase) }) { continue } delivery.Status.Points = append(delivery.Status.Points, *attempt) } else { if continuous == nil { continuous = &continuousStatus{} delivery.Status.Continuous = continuous } if previous := continuous.Attempt; previous != nil { if !terminalPhase(previous.Phase) { continue } if previous.CreatedAt != nil && previous.CreatedAt.After(*attempt.CreatedAt) { rememberAttempt(continuous, previous) addHistory(previous.History, attempt) recovered = true continue } } rememberAttempt(continuous, attempt) recordDelivered(continuous) } if attempt.RetryOf != "" && attempt.RetryOf == delivery.Metadata.Annotations[retryAnnotation] { delivery.Status.LastHandledRetry = attempt.RetryOf } observeQualBuild(delivery, attempt) recovered = true } return recovered, nil }