From aa0ae498deeff6e450013cf3d79a51284a04f2bf Mon Sep 17 00:00:00 2001 From: David Desmarais-Michaud Date: Mon, 10 Aug 2026 10:08:26 -0400 Subject: [PATCH] feat(PL-6661): implement release pruning respecting prune annotation --- cmd/operator/main_test.go | 217 +++++++++++++++++++++++-- cmd/operator/reconciler_environment.go | 25 ++- cmd/operator/reconciler_release.go | 57 ++++++- go.mod | 34 ++-- go.sum | 71 ++++---- 5 files changed, 334 insertions(+), 70 deletions(-) diff --git a/cmd/operator/main_test.go b/cmd/operator/main_test.go index 36734fc..3c09196 100644 --- a/cmd/operator/main_test.go +++ b/cmd/operator/main_test.go @@ -11,6 +11,7 @@ import ( "os" "os/exec" "path/filepath" + "slices" "strconv" "testing" "time" @@ -215,8 +216,8 @@ func TestMain(m *testing.M) { ObjectMeta: metav1.ObjectMeta{Name: "joy-operator-tests", Namespace: "default"}, }, k8s.WaitOptions{ - Interval: 250 * time.Millisecond, - Timeout: 15 * time.Second, + Interval: 500 * time.Millisecond, + Timeout: 30 * time.Second, }, ), ) @@ -238,7 +239,7 @@ func TestHappyReconciliations(t *testing.T) { Resource: "projects", }) - project, err := projectIntf.Create( + project, err := projectIntf.Apply( t.Context(), &v1alpha1.Project{ ApiVersion: v1alpha1.GroupVersion.Identifier(), @@ -250,7 +251,7 @@ func TestHappyReconciliations(t *testing.T) { Repository: "org/repo", }, }, - metav1.CreateOptions{}, + metav1.ApplyOptions{FieldManager: joyOperator}, ) require.NoError(t, err) @@ -260,7 +261,7 @@ func TestHappyReconciliations(t *testing.T) { Resource: "catalogs", }) - catalog, err := catalogIntf.Create( + catalog, err := catalogIntf.Apply( t.Context(), &v1alpha1.Catalog{ TypeMeta: metav1.TypeMeta{ @@ -286,7 +287,7 @@ func TestHappyReconciliations(t *testing.T) { }, }, }, - metav1.CreateOptions{}, + metav1.ApplyOptions{FieldManager: joyOperator}, ) require.NoError(t, err) @@ -296,7 +297,7 @@ func TestHappyReconciliations(t *testing.T) { Resource: "environments", }) - env, err := envIntf.Create( + env, err := envIntf.Apply( t.Context(), &v1alpha1.Environment{ ApiVersion: v1alpha1.GroupVersion.Identifier(), @@ -308,7 +309,7 @@ func TestHappyReconciliations(t *testing.T) { }, }, }, - metav1.CreateOptions{}, + metav1.ApplyOptions{FieldManager: joyOperator}, ) require.NoError(t, err) @@ -331,7 +332,7 @@ func TestHappyReconciliations(t *testing.T) { releaseIntf = releaseIntf.Namespace(env.Namespace) - release, err := releaseIntf.Create( + release, err := releaseIntf.Apply( t.Context(), &v1alpha1.Release{ Kind: v1alpha1.ReleaseGK.Kind, @@ -352,7 +353,7 @@ func TestHappyReconciliations(t *testing.T) { }, }, }, - metav1.CreateOptions{}, + metav1.ApplyOptions{FieldManager: joyOperator}, ) require.NoError(t, err) @@ -422,7 +423,7 @@ func TestHappyReconciliations(t *testing.T) { }, app.Spec.Destination, ) - require.Equal(t, new(argocd.SyncPolicyAutomated), app.Spec.SyncPolicy.Automated) + require.Equal(t, &argocd.SyncPolicyAutomated{Prune: new(true)}, app.Spec.SyncPolicy.Automated) }, "staging-test": func(t *testing.T, app *argocd.Application) { require.Equal(t, "argocd", app.Namespace) @@ -616,7 +617,7 @@ func TestEnvironmentSourcePattern(t *testing.T) { Resource: "catalogs", }) - _, err = catalogIntf.Create( + _, err = catalogIntf.Apply( t.Context(), &v1alpha1.Catalog{ TypeMeta: metav1.TypeMeta{ @@ -629,7 +630,7 @@ func TestEnvironmentSourcePattern(t *testing.T) { Revision: "main", }, }, - metav1.CreateOptions{}, + metav1.ApplyOptions{FieldManager: joyOperator}, ) require.NoError(t, err) @@ -685,6 +686,196 @@ func TestEnvironmentSourcePattern(t *testing.T) { ) } +func TestReleasePruning(t *testing.T) { + client, err := getKubeClient() + require.NoError(t, err) + + projectIntf := k8s.TypedInterface[v1alpha1.Project](client, schema.GroupVersionResource{ + Group: v1alpha1.ProjectGVK.Group, + Version: "v1alpha1", + Resource: "projects", + }) + + project, err := projectIntf.Apply( + t.Context(), + &v1alpha1.Project{ + ApiVersion: v1alpha1.GroupVersion.Identifier(), + Kind: v1alpha1.ProjectGK.Kind, + ProjectMetadata: v1alpha1.ProjectMetadata{ + ObjectMeta: metav1.ObjectMeta{Name: "test"}, + }, + Spec: v1alpha1.ProjectSpec{ + Repository: "org/repo", + }, + }, + metav1.ApplyOptions{FieldManager: joyOperator}, + ) + require.NoError(t, err) + + catalogIntf := k8s.TypedInterface[v1alpha1.Catalog](client, schema.GroupVersionResource{ + Group: v1alpha1.CatalogGK.Group, + Version: "v1alpha1", + Resource: "catalogs", + }) + + _, err = catalogIntf.Apply( + t.Context(), + &v1alpha1.Catalog{ + TypeMeta: metav1.TypeMeta{ + APIVersion: v1alpha1.GroupVersion.Identifier(), + Kind: v1alpha1.CatalogGK.Kind, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "catalog", + }, + Spec: v1alpha1.CatalogSpec{ + RepoURL: "https://github.com/testing/catalog", + Revision: "main", + Charts: v1alpha1.CatalogCharts{ + Default: "default", + Refs: map[string]helm.Chart{ + "default": { + Name: "alpha", + RepoURL: "file:///data/chart", + Version: "6.6.6", + Mappings: map[string]any{}, + }, + }, + }, + }, + }, + metav1.ApplyOptions{FieldManager: joyOperator}, + ) + require.NoError(t, err) + + envIntf := k8s.TypedInterface[v1alpha1.Environment](client, schema.GroupVersionResource{ + Group: v1alpha1.EnvironmentGK.Group, + Version: "v1alpha1", + Resource: "environments", + }) + + env, err := envIntf.Apply( + t.Context(), + &v1alpha1.Environment{ + ApiVersion: v1alpha1.GroupVersion.Identifier(), + Kind: v1alpha1.EnvironmentGK.Kind, + EnvironmentMetadata: v1alpha1.EnvironmentMetadata{ObjectMeta: metav1.ObjectMeta{Name: "staging"}}, + Spec: v1alpha1.EnvironmentSpec{ + Values: map[string]any{ + "env": "alpha", + }, + }, + }, + metav1.ApplyOptions{FieldManager: joyOperator}, + ) + require.NoError(t, err) + + EventuallyNoErrorf( + t, + func() error { + _, err := client.Clientset.CoreV1().Namespaces().Get(t.Context(), "staging", metav1.GetOptions{}) + return err + }, + 50*time.Millisecond, + 2*time.Second, + "failed to get corresponding namespace for env", + ) + + releaseIntf := k8s.TypedInterface[v1alpha1.Release](client, schema.GroupVersionResource{ + Group: v1alpha1.ReleaseGK.Group, + Version: "v1alpha1", + Resource: "releases", + }) + + releaseIntf = releaseIntf.Namespace(env.Name) + + release, err := releaseIntf.Apply( + t.Context(), + &v1alpha1.Release{ + Kind: v1alpha1.ReleaseGK.Kind, + ApiVersion: v1alpha1.GroupVersion.Identifier(), + ReleaseMetadata: v1alpha1.ReleaseMetadata{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: env.Name, + Annotations: map[string]string{ + "argocd.nesto.ca/sync.prune": "true", + }, + }, + }, + Spec: v1alpha1.ReleaseSpec{ + Project: project.Name, + Version: "1.2.3", + Namespace: "custom", + Values: map[string]any{ + "freehand": "hello", + "env": "{{ .Environment.Spec.Values.env }}", + }, + }, + }, + metav1.ApplyOptions{FieldManager: joyOperator}, + ) + require.NoError(t, err) + + EventuallyNoErrorf( + t, + func() error { + release, err := releaseIntf.Get(t.Context(), release.Name, metav1.GetOptions{}) + if err != nil { + return fmt.Errorf("failed to get release: %w", err) + } + if !slices.Contains(release.Finalizers, finalizerPruneRelease) { + return fmt.Errorf("release does not container prune-release finalizer") + } + return nil + }, + time.Second, + 5*time.Second, + "release never reached expected state", + ) + + appsIntf := k8s.TypedInterface[argocd.Application](client, argocd.ApplicationGVR).Namespace("argocd") + + EventuallyNoErrorf( + t, + func() error { + _, err := appsIntf.Get(t.Context(), "staging-test", metav1.GetOptions{}) + return err + }, + 50*time.Millisecond, + 2*time.Second, + "failed to get staging-test application", + ) + + rel, err := releaseIntf.Get(t.Context(), release.Name, metav1.GetOptions{}) + require.NoError(t, err) + require.Equal(t, []string{finalizerPruneRelease}, rel.Finalizers) + require.NoError(t, releaseIntf.Delete(t.Context(), release.Name, metav1.DeleteOptions{})) + + _, err = releaseIntf.Get(t.Context(), release.Name, metav1.GetOptions{}) + require.NoError(t, err) + + EventuallyNoErrorf( + t, + func() error { + release, err := appsIntf.Get(t.Context(), "staging-test", metav1.GetOptions{}) + if err != nil { + if kerrors.IsNotFound(err) { + return nil + } + return fmt.Errorf("expected app to be not found but got: %v", err) + } + if release.DeletionTimestamp.IsZero() { + return fmt.Errorf("expected application to have a deletion timestamp but did not") + } + return nil + }, + 500*time.Millisecond, + 10*time.Second, + "failed to see release application pruned", + ) +} + func EventuallyNoErrorf(t *testing.T, fn func() error, tick time.Duration, timeout time.Duration, msg string, args ...any) { var ( ticker = time.NewTimer(0) diff --git a/cmd/operator/reconciler_environment.go b/cmd/operator/reconciler_environment.go index 99d8cb9..81eb3e1 100644 --- a/cmd/operator/reconciler_environment.go +++ b/cmd/operator/reconciler_environment.go @@ -14,6 +14,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/util/retry" "github.com/nestoca/joy/api/v1alpha1" @@ -109,7 +110,9 @@ func EnvironmentReconciler(params EnvironmentReconcilerParams) ctrl.Funcs { Namespace: ns.Name, }, SyncPolicy: argocd.SyncPolicy{ - Automated: &argocd.SyncPolicyAutomated{}, + Automated: &argocd.SyncPolicyAutomated{ + Prune: new(true), + }, }, }, }, @@ -118,7 +121,25 @@ func EnvironmentReconciler(params EnvironmentReconcilerParams) ctrl.Funcs { return ctrl.Result{}, fmt.Errorf("failed to apply application: %w", err) } } else { - if err := appIntf.Delete(ctx, event.Name, metav1.DeleteOptions{}); err != nil && !kerrors.IsNotFound(err) { + if err := retry.RetryOnConflict(retry.DefaultBackoff, func() error { + app, err := appIntf.Get(ctx, event.Name, metav1.GetOptions{}) + if err != nil { + return fmt.Errorf("failed to get application: %w", err) + } + app.Spec.SyncPolicy = argocd.SyncPolicy{ + Automated: &argocd.SyncPolicyAutomated{ + Prune: new(false), + }, + } + + app, err = appIntf.Apply(ctx, app, metav1.ApplyOptions{FieldManager: joyOperator, Force: true}) + if err != nil { + return fmt.Errorf("failed to update syncPolicy to not prune: %w", err) + } + // We want a guarantee that when we delete the application that it was against a version of the resource who has prune de-activated. + // If we move from pull mode to non-pull mode, we don't want to drop all releases. + return appIntf.Delete(ctx, app.Name, metav1.DeleteOptions{Preconditions: &metav1.Preconditions{ResourceVersion: &app.ResourceVersion}}) + }); err != nil && !kerrors.IsNotFound(err) { return ctrl.Result{}, fmt.Errorf("failed to delete application: %w", err) } } diff --git a/cmd/operator/reconciler_release.go b/cmd/operator/reconciler_release.go index 407fa40..d263e5f 100644 --- a/cmd/operator/reconciler_release.go +++ b/cmd/operator/reconciler_release.go @@ -14,6 +14,7 @@ import ( kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" "github.com/nestoca/joy/api/v1alpha1" joy "github.com/nestoca/joy/pkg" @@ -33,6 +34,8 @@ type ReleaseReconcilerParams struct { EnvDestinations map[string]argocd.ApplicationDestination } +const finalizerPruneRelease = "joy.nesto.ca/prune-release" + func ReleaseReconciler(params ReleaseReconcilerParams) ctrl.Funcs { return ctrl.Funcs{ Handler: func(ctx context.Context, event ctrl.Event) (ctrl.Result, error) { @@ -43,7 +46,17 @@ func ReleaseReconciler(params ReleaseReconcilerParams) ctrl.Funcs { releaseCache := ctrl.CacheFromEvent[v1alpha1.Release](ctx, event) + releaseIntf := k8s.TypedInterface[v1alpha1.Release]( + ctrl.Client(ctx), + schema.GroupVersionResource{Group: v1alpha1.Group, Version: v1alpha1.Version, Resource: "releases"}, + ).Namespace(event.Namespace) + release, err := releaseCache.Get(event.Name) + if kerrors.IsNotFound(err) { + // If the release is no longer in the cache, a deletion event happened. However, that doesn't mean that the release doesn't yet + // still exist in etcd with a deletion timestamp and finalizer. In order to handle cleanup, we need to check its live state. + release, err = releaseIntf.Get(ctx, event.Name, metav1.GetOptions{}) + } if err != nil { if kerrors.IsNotFound(err) { return ctrl.Result{}, nil @@ -64,6 +77,42 @@ func ReleaseReconciler(params ReleaseReconcilerParams) ctrl.Funcs { return ctrl.Result{}, fmt.Errorf("failed to get project: %w", err) } + var ( + isDeleted = !release.DeletionTimestamp.IsZero() + hasPruneReleaseFinalizer = slices.Contains(release.Finalizers, finalizerPruneRelease) + shouldPrune = release.Annotations["argocd.nesto.ca/sync.prune"] == "true" + ) + + switch { + case shouldPrune && !hasPruneReleaseFinalizer: + release.Finalizers = append(release.Finalizers, finalizerPruneRelease) + if _, err := releaseIntf.Apply(ctx, release, metav1.ApplyOptions{FieldManager: joyOperator, Force: true}); err != nil { + return ctrl.Result{}, fmt.Errorf("failed to apply prune-release finalizer: %w", err) + } + return ctrl.Result{}, nil + case !shouldPrune && hasPruneReleaseFinalizer: + release.Finalizers = slices.DeleteFunc(release.Finalizers, func(finalizer string) bool { return finalizer == finalizerPruneRelease }) + if _, err := releaseIntf.Apply(ctx, release, metav1.ApplyOptions{FieldManager: joyOperator, Force: true}); err != nil { + return ctrl.Result{}, fmt.Errorf("failed to remove prune-release finalizer: %w", err) + } + return ctrl.Result{}, nil + } + + appIntf := k8s.TypedInterface[argocd.Application](ctrl.Client(ctx), argocd.ApplicationGVR).Namespace("argocd") + + if isDeleted && shouldPrune { + if err := appIntf.Delete(ctx, appName(release), metav1.DeleteOptions{}); err != nil && !kerrors.IsNotFound(err) { + return ctrl.Result{}, fmt.Errorf("failed to delete application: %w", err) + } + if hasPruneReleaseFinalizer { + release.Finalizers = slices.DeleteFunc(release.Finalizers, func(finalizer string) bool { return finalizer == finalizerPruneRelease }) + if _, err := releaseIntf.Apply(ctx, release, metav1.ApplyOptions{FieldManager: joyOperator, Force: true}); err != nil { + return ctrl.Result{}, fmt.Errorf("failed to remove prune-release finalizer post app deletion: %w", err) + } + } + return ctrl.Result{}, nil + } + catalogCache := ctrl.Cache[v1alpha1.Catalog](ctx, v1alpha1.CatalogGK, "") catalog, err := catalogCache.Get(params.CatalogName) @@ -100,8 +149,6 @@ func ReleaseReconciler(params ReleaseReconcilerParams) ctrl.Funcs { Chart: chartFS.Chart, }) - appIntf := k8s.TypedInterface[argocd.Application](ctrl.Client(ctx), argocd.ApplicationGVR).Namespace("argocd") - if _, err := appIntf.Apply(ctx, &app, metav1.ApplyOptions{FieldManager: joyOperator, Force: true}); err != nil { return ctrl.Result{}, fmt.Errorf("failed to apply application: %w", err) } @@ -156,7 +203,7 @@ func renderReleaseApplication(params RenderApplicationParams) argocd.Application APIVersion: "argoproj.io/v1alpha1", }, ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("%s-%s", params.Release.Environment.Name, params.Release.Name), + Name: appName(params.Release), Namespace: "argocd", Finalizers: []string{"resources-finalizer.argocd.argoproj.io"}, Labels: map[string]string{ @@ -235,3 +282,7 @@ func ValueEqualsOr(m map[string]string, key, expected string, fallback *bool) *b } return new(actual == expected) } + +func appName(release *v1alpha1.Release) string { + return fmt.Sprintf("%s-%s", release.Environment.Name, release.Name) +} diff --git a/go.mod b/go.mod index b8e780a..a0376a3 100644 --- a/go.mod +++ b/go.mod @@ -9,12 +9,12 @@ require ( github.com/davidmdm/x/xsync v0.0.3 github.com/nestoca/joy v0.96.0 github.com/stretchr/testify v1.11.1 - github.com/yokecd/yoke v0.20.23 - go.yaml.in/yaml/v3 v3.0.4 - k8s.io/api v0.36.2 - k8s.io/apiextensions-apiserver v0.36.2 - k8s.io/apimachinery v0.36.2 - k8s.io/client-go v0.36.2 + github.com/yokecd/yoke v0.20.25 + go.yaml.in/yaml/v3 v3.0.5 + k8s.io/api v0.36.3 + k8s.io/apiextensions-apiserver v0.36.3 + k8s.io/apimachinery v0.36.3 + k8s.io/client-go v0.36.3 ) require ( @@ -42,7 +42,7 @@ require ( github.com/go-errors/errors v1.4.2 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.9.0 // indirect - github.com/go-git/go-git/v5 v5.19.1 // indirect + github.com/go-git/go-git/v5 v5.19.2 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect @@ -54,7 +54,7 @@ require ( github.com/huandu/xstrings v1.5.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect - github.com/jedib0t/go-pretty/v6 v6.8.1 // indirect + github.com/jedib0t/go-pretty/v6 v6.8.3 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect @@ -90,13 +90,13 @@ require ( go.opentelemetry.io/otel/trace v1.43.0 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect golang.org/x/crypto v0.53.0 // indirect - golang.org/x/mod v0.37.0 // indirect - golang.org/x/net v0.55.0 // indirect + golang.org/x/mod v0.38.0 // indirect + golang.org/x/net v0.56.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect - golang.org/x/sync v0.21.0 // indirect - golang.org/x/sys v0.46.0 // indirect - golang.org/x/term v0.44.0 // indirect - golang.org/x/text v0.38.0 // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/sys v0.47.0 // indirect + golang.org/x/term v0.45.0 // indirect + golang.org/x/text v0.39.0 // indirect golang.org/x/time v0.14.0 // indirect google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect @@ -104,14 +104,14 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/cli-runtime v0.36.1 // indirect + k8s.io/cli-runtime v0.36.3 // indirect k8s.io/klog/v2 v2.140.0 // indirect k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect - k8s.io/utils v0.0.0-20260507154919-ff6756f316d2 // indirect + k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/kustomize/api v0.21.1 // indirect sigs.k8s.io/kustomize/kyaml v0.21.1 // indirect sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.3 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/go.sum b/go.sum index 53ff039..40f2ea7 100644 --- a/go.sum +++ b/go.sum @@ -67,8 +67,8 @@ github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66D github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.9.0 h1:jItGXszUDRtR/AlferWPTMN4j38BQ88XnXKbilmmBPA= github.com/go-git/go-billy/v5 v5.9.0/go.mod h1:jCnQMLj9eUgGU7+ludSTYoZL/GGmii14RxKFj7ROgHw= -github.com/go-git/go-git/v5 v5.19.1 h1:nX27AnaU43/K5bKktKwgBmR9lawoYVe1Ckg0rgzzN00= -github.com/go-git/go-git/v5 v5.19.1/go.mod h1:Pb1v0c7/g8aGQJwx9Us09W85yGoyvSwuhEGMH7zjDKQ= +github.com/go-git/go-git/v5 v5.19.2 h1:wkfn7vOlUBu8ivAWKBWisTiwJK4jYHzTF8Ndv1LyGqY= +github.com/go-git/go-git/v5 v5.19.2/go.mod h1:QqCBE1EFN5ddFmrliLQ3/ntRCUjZU3EJuwuB/jWEHjk= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -101,8 +101,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= -github.com/jedib0t/go-pretty/v6 v6.8.1 h1:0fkCNhjrX0zPpwkWaDYU5VMrygg41Tu197mWILIJoqQ= -github.com/jedib0t/go-pretty/v6 v6.8.1/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU= +github.com/jedib0t/go-pretty/v6 v6.8.3 h1:yVSk5aemoYHCvcrtqyXklwqcgHQIQzmy/oUzFlmffSQ= +github.com/jedib0t/go-pretty/v6 v6.8.3/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -204,8 +204,8 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= -github.com/yokecd/yoke v0.20.23 h1:MtxmOC1faWzomCpy2FKNbJ3eizhqtKNIR63crMIGUFg= -github.com/yokecd/yoke v0.20.23/go.mod h1:/DMxDjT1JuRPurCmPHPO10UxiN56XHHOzz/mz8RUICQ= +github.com/yokecd/yoke v0.20.25 h1:EZBh22mvp04Z/qLG4ilBiuc7FNoUuqPsbPbcB27CL8s= +github.com/yokecd/yoke v0.20.25/go.mod h1:5juXF444d4ijVVDXn/r26W6qqxksA7Tp0ok4uiP5Zcc= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= @@ -219,26 +219,27 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= -go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= -golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= +golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk= +golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= -golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= +golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= +golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= -golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -247,25 +248,25 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= -golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc= -golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= +golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= +golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= -golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= +golang.org/x/text v0.39.0 h1:UbZz4pLOvn600D6Oh6GGEI6VAmndrEBLv8/6BEXzyus= +golang.org/x/text v0.39.0/go.mod h1:3UwRclnC2g0TU9x8PZiyfOajCd1zaUNHF9cvqcQZ+ZM= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= -golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= +golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q= +golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= @@ -283,22 +284,22 @@ gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRN gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.36.2 h1:TF6YDLIzKfccK7cq9YpTcGX8TJmEkHVRv78DM51fRYY= -k8s.io/api v0.36.2/go.mod h1:F4LbMO4brjZYh7yFkXWhynSvtB7YauxV4c+HHkNRGNg= -k8s.io/apiextensions-apiserver v0.36.2 h1:3O5gqOj/dt2XWWbpMe+TXWpE9yU6pjM/tXxtHHJT/K4= -k8s.io/apiextensions-apiserver v0.36.2/go.mod h1:cL1tBWe8XSaP1H30iWKGo7hf6iAUUUJPEU70dskmAnA= -k8s.io/apimachinery v0.36.2 h1:0PE/W/WNy1UX61NLbXY5TMbJ6UwLL6E6lAPkYrKFxbQ= -k8s.io/apimachinery v0.36.2/go.mod h1:fvf/HOLXq9RId0rnDIbN1OEBvHXdQbLMM8nu0LcBUf4= -k8s.io/cli-runtime v0.36.1 h1:yuC/BGnnj1YYPh6D1P+pZnzinCs6DvMq86yAeNqoqzM= -k8s.io/cli-runtime v0.36.1/go.mod h1:ZQWHGt8xAF7KnviB79vX0lYNyUUqKIpU+LQg7exuFAw= -k8s.io/client-go v0.36.2 h1:bfgxmFKc9CgqsgX4xKLAAdmTQlWee7Ob/HlDOrJ5TBI= -k8s.io/client-go v0.36.2/go.mod h1:1vgO4OAlfPnoLcb+Rze2GF5rAr14w8qjrYMoyXJzQj0= +k8s.io/api v0.36.3 h1:NxB+05W2UGqXWFXcLO0RB5cnqnUPP5v5sVlaOH0Iz4w= +k8s.io/api v0.36.3/go.mod h1:JzLQKqRHC5+I8RVj/lS3lCg0mg6nWI9Fo/Sk3ElxHzg= +k8s.io/apiextensions-apiserver v0.36.3 h1:dPmOAPhwTtqb1bTxbFPsy18KHPhktQeO3WUPXunZIB0= +k8s.io/apiextensions-apiserver v0.36.3/go.mod h1:KTXFqgXiuw2pRoL+Wpmttqc+up9Xt/GohadPWeLLOa4= +k8s.io/apimachinery v0.36.3 h1:PkzMRBRG8joFD8EhCuQAtNPvJlxb82FwplP26HIzvAM= +k8s.io/apimachinery v0.36.3/go.mod h1:cTSjBWgPe/6CQyBKzY/hDIRWCQQQeK0mfLbml0UYFHE= +k8s.io/cli-runtime v0.36.3 h1:g+eJ+M1sYpnNYp/q5fzaw2KejIL0Q7DH+xFl6YVoL4U= +k8s.io/cli-runtime v0.36.3/go.mod h1:hZpAqK8nSFXvvLaVCbzUPVp8e9TRLSTCfpNzMt7s3tE= +k8s.io/client-go v0.36.3 h1:M4JdVzXxYcZk4fGpfDdYnxSwhLKWCFoQsHW6t+z8Hfg= +k8s.io/client-go v0.36.3/go.mod h1:gcPwr0c87vjjG6HB6pWEqOeuYVoXSsREjzux2j6GF30= k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg= k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= -k8s.io/utils v0.0.0-20260507154919-ff6756f316d2 h1:wU4tMEhLGgIbLvXQb1cfN+EcM0wf7zC6CPF+C79jroc= -k8s.io/utils v0.0.0-20260507154919-ff6756f316d2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= +k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3 h1:jVkFFVfXdXP74B/zbO3hM3hpSFD0xvhQ5U686DPurkE= +k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3/go.mod h1:M2s5JB1lIYP3jzZdorPLHXIPJzt9vv2muW5a6L9DtNM= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/kustomize/api v0.21.1 h1:lzqbzvz2CSvsjIUZUBNFKtIMsEw7hVLJp0JeSIVmuJs= @@ -307,7 +308,7 @@ sigs.k8s.io/kustomize/kyaml v0.21.1 h1:IVlbmhC076nf6foyL6Taw4BkrLuEsXUXNpsE+ScX7 sigs.k8s.io/kustomize/kyaml v0.21.1/go.mod h1:hmxADesM3yUN2vbA5z1/YTBnzLJ1dajdqpQonwBL1FQ= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v6 v6.3.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8= -sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/structured-merge-diff/v6 v6.3.3 h1:u08YRbVUi59ri4YD6cg0UqNM4Dimn0sIl+wldcx5PYw= +sigs.k8s.io/structured-merge-diff/v6 v6.3.3/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=