diff --git a/api/v1alpha1/condition_types.go b/api/v1alpha1/condition_types.go index a3fb88f..f1a89a4 100644 --- a/api/v1alpha1/condition_types.go +++ b/api/v1alpha1/condition_types.go @@ -60,6 +60,9 @@ const ( // ApplyFailedReason represents the fact that the kustomization apply failed. ApplyFailedReason string = "ApplyFailed" + // PruneFailedReason represents the fact that the kustomization pruning failed. + PruneFailedReason string = "PruneFailed" + // ArtifactFailedReason represents the fact that the artifact download failed. ArtifactFailedReason string = "ArtifactFailed" diff --git a/api/v1alpha1/kustomization_types.go b/api/v1alpha1/kustomization_types.go index d059d28..8103ba9 100644 --- a/api/v1alpha1/kustomization_types.go +++ b/api/v1alpha1/kustomization_types.go @@ -163,6 +163,20 @@ func KustomizationNotReady(kustomization Kustomization, reason, message string) return kustomization } +func KustomizationNotReadySnapshot(kustomization Kustomization, snapshot *Snapshot, reason, message string) Kustomization { + kustomization.Status.Conditions = []Condition{ + { + Type: ReadyCondition, + Status: corev1.ConditionFalse, + LastTransitionTime: metav1.Now(), + Reason: reason, + Message: message, + }, + } + kustomization.Status.Snapshot = snapshot + return kustomization +} + // GetTimeout returns the timeout with default func (in *Kustomization) GetTimeout() time.Duration { duration := in.Spec.Interval.Duration diff --git a/controllers/kustomization_controller.go b/controllers/kustomization_controller.go index 1a41472..21f248f 100644 --- a/controllers/kustomization_controller.go +++ b/controllers/kustomization_controller.go @@ -248,11 +248,22 @@ func (r *KustomizationReconciler) sync( ), err } - // health assessment - err = r.checkHealth(kustomization) + // prune + err = r.prune(kustomization, snapshot) if err != nil { return kustomizev1.KustomizationNotReady( kustomization, + kustomizev1.PruneFailedReason, + err.Error(), + ), err + } + + // health assessment + err = r.checkHealth(kustomization) + if err != nil { + return kustomizev1.KustomizationNotReadySnapshot( + kustomization, + snapshot, kustomizev1.HealthCheckFailedReason, "health check failed", ), err @@ -364,7 +375,7 @@ func (r *KustomizationReconciler) generateLabelTransformer(kustomization kustomi }{ Name: kustomization.GetName(), }, - Labels: gcLabels(kustomization.GetName(), revision), + Labels: gcLabels(kustomization.GetName(), kustomization.GetNamespace(), revision), FieldSpecs: []kustypes.FieldSpec{ {Path: "metadata/labels", CreateIfNotPresent: true}, }, @@ -476,6 +487,25 @@ func (r *KustomizationReconciler) apply(kustomization kustomizev1.Kustomization, return nil } +func (r *KustomizationReconciler) prune(kustomization kustomizev1.Kustomization, snapshot *kustomizev1.Snapshot) error { + if kustomization.Status.Snapshot == nil || snapshot == nil { + return nil + } + if kustomization.Status.Snapshot.Revision == snapshot.Revision { + return nil + } + + if !prune(kustomization.GetTimeout(), + kustomization.GetName(), + kustomization.GetNamespace(), + kustomization.Status.Snapshot, + r.Log, + ) { + return fmt.Errorf("pruning failed") + } + return nil +} + func (r *KustomizationReconciler) checkHealth(kustomization kustomizev1.Kustomization) error { timeout := kustomization.GetTimeout() + (time.Second * 1) ctx, cancel := context.WithTimeout(context.Background(), timeout) diff --git a/controllers/kustomization_predicate.go b/controllers/kustomization_predicate.go index 33fc34f..fad15e3 100644 --- a/controllers/kustomization_predicate.go +++ b/controllers/kustomization_predicate.go @@ -68,41 +68,62 @@ type KustomizationGarbageCollectPredicate struct { func (gc KustomizationGarbageCollectPredicate) Delete(e event.DeleteEvent) bool { if k, ok := e.Object.(*kustomizev1.Kustomization); ok { if k.Spec.Prune != "" && !k.Spec.Suspend && k.Status.Snapshot != nil { - timeout := k.GetTimeout() - selector := gcSelectors(k.GetName(), k.Status.Snapshot.Revision) gc.Log.Info("Garbage collection started", "kustomization", fmt.Sprintf("%s/%s", k.GetNamespace(), k.GetName())) - for ns, kinds := range k.Status.Snapshot.NamespacedKinds() { - for _, kind := range kinds { - if output, err := deleteByKind(timeout, kind, ns, selector); err != nil { - gc.Log.Error(err, "Garbage collection failed for namespaced objects", - "kustomization", fmt.Sprintf("%s/%s", k.GetNamespace(), k.GetName())) - } else { - gc.Log.Info("Garbage collection for namespaced objects completed", - "kustomization", fmt.Sprintf("%s/%s", k.GetNamespace(), k.GetName()), - "output", output) - } - } - } - - for _, kind := range k.Status.Snapshot.NonNamespacedKinds() { - if output, err := deleteByKind(timeout, kind, "", selector); err != nil { - gc.Log.Error(err, "Garbage collection failed for non-namespaced objects", - "kustomization", fmt.Sprintf("%s/%s", k.GetNamespace(), k.GetName())) - } else { - gc.Log.Info("Garbage collection for non-namespaced objects completed", - "kustomization", fmt.Sprintf("%s/%s", k.GetNamespace(), k.GetName()), - "output", output) - } - - } + prune(k.GetTimeout(), k.GetName(), k.GetNamespace(), k.Status.Snapshot, gc.Log) } } return true } +func prune(timeout time.Duration, name string, namespace string, snapshot *kustomizev1.Snapshot, log logr.Logger) bool { + selector := gcSelectors(name, namespace, snapshot.Revision) + ok := true + outInfo := "" + outErr := "" + for ns, kinds := range snapshot.NamespacedKinds() { + for _, kind := range kinds { + if output, err := deleteByKind(timeout, kind, ns, selector); err != nil { + outErr += " " + err.Error() + ok = false + } else { + outInfo += " " + output + } + } + } + if outErr == "" { + log.Info("Garbage collection for namespaced objects completed", + "kustomization", fmt.Sprintf("%s/%s", namespace, name), + "output", outInfo) + } else { + log.Error(fmt.Errorf(outErr), "Garbage collection for namespaced objects failed", + "kustomization", fmt.Sprintf("%s/%s", namespace, name)) + } + + outInfo = "" + outErr = "" + for _, kind := range snapshot.NonNamespacedKinds() { + if output, err := deleteByKind(timeout, kind, "", selector); err != nil { + outErr += " " + err.Error() + ok = false + } else { + outInfo += " " + output + } + } + if outErr == "" { + log.Info("Garbage collection for non-namespaced objects completed", + "kustomization", fmt.Sprintf("%s/%s", namespace, name), + "output", outInfo) + } else { + log.Error(fmt.Errorf(outErr), "Garbage collection for non-namespaced objects failed", + "kustomization", fmt.Sprintf("%s/%s", namespace, name)) + } + + return ok +} + func deleteByKind(timeout time.Duration, kind, namespace, selector string) (string, error) { ctx, cancel := context.WithTimeout(context.Background(), timeout+time.Second) defer cancel() @@ -120,15 +141,15 @@ func deleteByKind(timeout time.Duration, kind, namespace, selector string) (stri } } -func gcLabels(name, revision string) map[string]string { +func gcLabels(name, namespace, revision string) map[string]string { return map[string]string{ - "kustomization/name": name, + "kustomization/name": fmt.Sprintf("%s-%s", name, namespace), "kustomization/revision": checksum(revision), } } -func gcSelectors(name, revision string) string { - return fmt.Sprintf("kustomization/name=%s,kustomization/revision=%s", name, checksum(revision)) +func gcSelectors(name, namespace, revision string) string { + return fmt.Sprintf("kustomization/name=%s-%s,kustomization/revision=%s", name, namespace, checksum(revision)) } func checksum(in string) string {