quorate/services/project-delivery/controller/records.go

97 lines
3.2 KiB
Go

package main
import (
"context"
"encoding/json"
"slices"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/types"
)
const recordKey = "delivery.quorate.dev/record"
func (c *controller) releaseRecords(ctx context.Context, delivery *projectDelivery) error {
known := make(map[string]bool)
for _, attempt := range delivery.Status.Points {
known[attempt.WorkflowName] = true
}
if status := delivery.Status.Continuous; status != nil && status.Attempt != nil {
known[status.Attempt.WorkflowName] = true
if status.Attempt.History != nil {
for _, name := range status.Attempt.History.Workflows {
known[name] = true
}
}
}
if status := delivery.Status.Qualification; status != nil {
for _, attempt := range status.Attempts {
if attempt.Phase != "Pending" && attempt.Phase != "Reserved" {
known[attempt.WorkflowName] = true
}
}
}
items, err := c.client.Resource(workflowGVR).Namespace(delivery.Metadata.Namespace).List(ctx, metav1.ListOptions{LabelSelector: recordKey + "=pending"})
if err != nil {
return err
}
for _, item := range items.Items {
if known[item.GetName()] && ownedBy(&item, delivery.Metadata.UID) {
if err := c.clearRecord(ctx, &item); err != nil {
return err
}
}
}
return nil
}
func (c *controller) releaseOrphans(ctx context.Context) error {
items, err := c.client.Resource(workflowGVR).Namespace(metav1.NamespaceAll).List(ctx, metav1.ListOptions{LabelSelector: recordKey + "=pending"})
if err != nil {
return err
}
for _, item := range items.Items {
for _, owner := range item.GetOwnerReferences() {
if owner.Kind != "ProjectDelivery" || owner.APIVersion != "delivery.quorate.dev/v1alpha1" {
continue
}
parent, err := c.client.Resource(projectDeliveryGVR).Namespace(item.GetNamespace()).Get(ctx, owner.Name, metav1.GetOptions{})
if apierrors.IsNotFound(err) || err == nil && (parent.GetUID() != owner.UID || parent.GetDeletionTimestamp() != nil) {
if err := c.clearRecord(ctx, &item); err != nil {
return err
}
} else if err != nil {
return err
}
}
}
return nil
}
func ownedBy(workflow *unstructured.Unstructured, uid string) bool {
return slices.ContainsFunc(workflow.GetOwnerReferences(), func(owner metav1.OwnerReference) bool {
return owner.Kind == "ProjectDelivery" && owner.APIVersion == "delivery.quorate.dev/v1alpha1" && string(owner.UID) == uid
})
}
func (c *controller) clearRecord(ctx context.Context, workflow *unstructured.Unstructured) error {
finalizers := slices.DeleteFunc(slices.Clone(workflow.GetFinalizers()), func(name string) bool { return name == recordKey })
if finalizers == nil {
finalizers = []string{}
}
patch, err := json.Marshal(map[string]any{"metadata": map[string]any{
"resourceVersion": workflow.GetResourceVersion(),
"finalizers": finalizers,
"labels": map[string]any{recordKey: nil},
}})
if err != nil {
return err
}
_, err = c.client.Resource(workflowGVR).Namespace(workflow.GetNamespace()).Patch(ctx, workflow.GetName(), types.MergePatchType, patch, metav1.PatchOptions{})
if apierrors.IsNotFound(err) {
return nil
}
return err
}