diff --git a/apis/meta/conditions.go b/apis/meta/conditions.go index d326ee77..9b65c5b5 100644 --- a/apis/meta/conditions.go +++ b/apis/meta/conditions.go @@ -156,6 +156,10 @@ const ( // FeatureGateDisabledReason represents the fact that a feature is trying to // be used, but the feature gate for that feature is disabled. FeatureGateDisabledReason string = "FeatureGateDisabled" + + // HealthCheckCanceledReason represents the fact that + // the health check was canceled. + HealthCheckCanceledReason string = "HealthCheckCanceled" ) // ObjectWithConditions describes a Kubernetes resource object with status conditions. diff --git a/artifact/go.mod b/artifact/go.mod index 86d9aad9..879c5106 100644 --- a/artifact/go.mod +++ b/artifact/go.mod @@ -13,7 +13,7 @@ replace ( require ( github.com/cyphar/filepath-securejoin v0.4.1 - github.com/fluxcd/pkg/apis/meta v1.22.0 + github.com/fluxcd/pkg/apis/meta v1.23.0 github.com/fluxcd/pkg/lockedfile v0.7.0 github.com/fluxcd/pkg/oci v0.57.0 github.com/fluxcd/pkg/sourceignore v0.15.0 diff --git a/auth/go.mod b/auth/go.mod index 95699e98..3fdc5cde 100644 --- a/auth/go.mod +++ b/auth/go.mod @@ -21,7 +21,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/eks v1.74.2 github.com/aws/aws-sdk-go-v2/service/sts v1.38.6 github.com/coreos/go-oidc/v3 v3.16.0 - github.com/fluxcd/pkg/apis/meta v1.22.0 + github.com/fluxcd/pkg/apis/meta v1.23.0 github.com/fluxcd/pkg/cache v0.12.0 github.com/golang-jwt/jwt/v5 v5.3.0 github.com/google/go-containerregistry v0.20.6 diff --git a/chartutil/go.mod b/chartutil/go.mod index ec2590b8..9c47ee36 100644 --- a/chartutil/go.mod +++ b/chartutil/go.mod @@ -9,7 +9,7 @@ replace github.com/fluxcd/pkg/apis/meta => ../apis/meta replace github.com/opencontainers/go-digest => github.com/opencontainers/go-digest v1.0.1-0.20231025023718-d50d2fec9c98 require ( - github.com/fluxcd/pkg/apis/meta v1.22.0 + github.com/fluxcd/pkg/apis/meta v1.23.0 github.com/go-logr/logr v1.4.3 github.com/onsi/gomega v1.38.2 github.com/opencontainers/go-digest v1.0.0 diff --git a/runtime/controller/builder.go b/runtime/controller/builder.go new file mode 100644 index 00000000..b7028af2 --- /dev/null +++ b/runtime/controller/builder.go @@ -0,0 +1,76 @@ +/* +Copyright 2025 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "context" + "strings" + + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/apiutil" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/source" +) + +// controllerBuilder wraps a *builder.Builder to +// enhance it with additional functionality. +type controllerBuilder struct { + *builder.Builder + mgr manager.Manager + reconciler *reconcilerWrapper +} + +// NewControllerManagedBy returns a wrapped *builder.Builder +// that facilitates building a controller for a specific +// object type harvesting the capabilities of the reconciler +// wrapper. +func NewControllerManagedBy(mgr manager.Manager, r *reconcilerWrapper) *controllerBuilder { + return &controllerBuilder{ + Builder: ctrl.NewControllerManagedBy(mgr), + mgr: mgr, + reconciler: r, + } +} + +// For is similar to builder.Builder.For, but internally +// uses WatchesRawSource to set up the watch harvesting +// the capabilities of the reconciler wrapper. +func (c *controllerBuilder) For(obj client.Object, pred predicate.Predicate) *controllerBuilder { + // Do the same as builder.Builder.For to define the controller name, + // lowercased kind of the object being watched. + gvk, err := apiutil.GVKForObject(obj, c.mgr.GetScheme()) + // Here we need to panic because builder.Builder.For does not return an error. + // This panic is fine, as it is caught during the controller initialization. + if err != nil { + panic(err) + } + name := strings.ToLower(gvk.Kind) + + c.Named(name) + c.WatchesRawSource(source.Kind( + c.mgr.GetCache(), + obj, + c.reconciler.EnqueueRequestsFromMapFunc(gvk.Kind, func(ctx context.Context, obj client.Object) []ctrl.Request { + return []ctrl.Request{{NamespacedName: client.ObjectKeyFromObject(obj)}} + }), + pred, + )) + return c +} diff --git a/runtime/controller/builder_test.go b/runtime/controller/builder_test.go new file mode 100644 index 00000000..329d921b --- /dev/null +++ b/runtime/controller/builder_test.go @@ -0,0 +1,97 @@ +/* +Copyright 2025 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller_test + +import ( + "context" + "sync/atomic" + "testing" + "time" + + "github.com/fluxcd/pkg/runtime/controller" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + "sigs.k8s.io/controller-runtime/pkg/predicate" +) + +type noopReconciler struct { + reconciled atomic.Bool +} + +func (r *noopReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + if req.Name == "test-configmap" && req.Namespace == "default" { + r.reconciled.Store(true) + } + return ctrl.Result{}, nil +} + +func TestControllerBuilder(t *testing.T) { + g := NewWithT(t) + + // Create test environment. + testEnv := &envtest.Environment{} + conf, err := testEnv.Start() + g.Expect(err).NotTo(HaveOccurred()) + t.Cleanup(func() { testEnv.Stop() }) + kubeClient, err := client.New(conf, client.Options{}) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(kubeClient).NotTo(BeNil()) + + // Create manager. + mgr, err := ctrl.NewManager(conf, ctrl.Options{}) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(mgr).NotTo(BeNil()) + + // Create and setup controller. + nr := &noopReconciler{} + r := controller.WrapReconciler(nr) + err = controller.NewControllerManagedBy(mgr, r). + For(&corev1.ConfigMap{}, predicate.ResourceVersionChangedPredicate{}). + Complete(r) + g.Expect(err).NotTo(HaveOccurred()) + + // Start manager. + errCh := make(chan error, 1) + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + go func() { + errCh <- mgr.Start(ctx) + close(errCh) + }() + + // Create a ConfigMap and expect the reconciler to be called. + g.Expect(nr.reconciled.Load()).To(BeFalse()) + g.Expect(kubeClient.Create(ctx, &corev1.ConfigMap{ + ObjectMeta: ctrl.ObjectMeta{ + Name: "test-configmap", + Namespace: "default", + }, + })).To(Succeed()) + g.Eventually(func() bool { return nr.reconciled.Load() }, time.Second).To(BeTrue()) + + // Stop the manager. + cancel() + select { + case err := <-errCh: + g.Expect(err).NotTo(HaveOccurred()) + case <-time.After(time.Second): + t.Fatal("timeout waiting for manager to stop") + } +} diff --git a/runtime/controller/queue.go b/runtime/controller/queue.go new file mode 100644 index 00000000..2cad74de --- /dev/null +++ b/runtime/controller/queue.go @@ -0,0 +1,133 @@ +/* +Copyright 2025 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "context" + "fmt" + "sync" + + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" +) + +// QueueEventSource holds enough tracking information about the +// source object that triggered a queue event and implements the +// error interface. +type QueueEventSource struct { + Kind string `json:"kind"` + Name string `json:"name"` + Namespace string `json:"namespace"` + UID types.UID `json:"uid"` + ResourceVersion string `json:"resourceVersion"` +} + +// Ensure QueueEventSource implements the error interface. +var _ error = &QueueEventSource{} + +// Error returns a string representation of the object represented by QueueEventSource. +func (q *QueueEventSource) Error() string { + return fmt.Sprintf("%s/%s/%s", q.Kind, q.Namespace, q.Name) +} + +// Is returns true if the target error is a QueueEventSource object. +func (*QueueEventSource) Is(target error) bool { + _, ok := target.(*QueueEventSource) + return ok +} + +// queueEventType represents the type of event that occurred in the queue. +type queueEventType int + +const ( + // queueEventObjectEnqueued indicates that an object was enqueued. + queueEventObjectEnqueued queueEventType = iota +) + +// queueEventPayload is the payload delivered to listeners +// when a queue event occurs. +type queueEventPayload struct { + source QueueEventSource +} + +// queueHooks implements mechanisms for hooking to queue events. +type queueHooks struct { + lis map[queueEvent][]*queueListener + mu sync.Mutex +} + +// queueEvent represents an event related to the queue. +type queueEvent struct { + queueEventType + ctrl.Request +} + +// queueListener represents a listener for a queue event. +type queueListener struct { + ctx context.Context + cancel context.CancelFunc + payload chan<- *queueEventPayload +} + +func newQueueHooks() *queueHooks { + return &queueHooks{ + lis: make(map[queueEvent][]*queueListener), + } +} + +func (q *queueHooks) dispatch(event queueEvent, payload queueEventPayload) { + q.mu.Lock() + listeners := q.lis[event] + delete(q.lis, event) + q.collectGarbage() + q.mu.Unlock() + + for _, l := range listeners { + l.payload <- &payload + l.cancel() + } +} + +func (q *queueHooks) registerListener(ctx context.Context, event queueEvent) ( + context.Context, context.CancelFunc, <-chan *queueEventPayload, +) { + ctx, cancel := context.WithCancel(ctx) + payload := make(chan *queueEventPayload, 1) + + q.mu.Lock() + q.collectGarbage() + q.lis[event] = append(q.lis[event], &queueListener{ctx, cancel, payload}) + q.mu.Unlock() + + return ctx, cancel, payload +} + +func (q *queueHooks) collectGarbage() { + for key, listeners := range q.lis { + var alive []*queueListener + for _, l := range listeners { + if l.ctx.Err() == nil { + alive = append(alive, l) + } + } + if len(alive) > 0 { + q.lis[key] = alive + } else { + delete(q.lis, key) + } + } +} diff --git a/runtime/controller/queue_test.go b/runtime/controller/queue_test.go new file mode 100644 index 00000000..1bbe5627 --- /dev/null +++ b/runtime/controller/queue_test.go @@ -0,0 +1,45 @@ +/* +Copyright 2025 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller_test + +import ( + "testing" + + . "github.com/onsi/gomega" + + "github.com/fluxcd/pkg/runtime/controller" +) + +func TestQueueEventSource_Error(t *testing.T) { + g := NewWithT(t) + var err error = &controller.QueueEventSource{ + Kind: "TestKind", + Name: "test-name", + Namespace: "test-namespace", + UID: "12345", + ResourceVersion: "1", + } + g.Expect(err.Error()).To(Equal("TestKind/test-namespace/test-name")) +} + +func TestQueueEventSource_Is(t *testing.T) { + g := NewWithT(t) + qes := controller.QueueEventSource{ + Kind: "TestKind", + } + g.Expect(qes.Is(&controller.QueueEventSource{})).To(BeTrue()) +} diff --git a/runtime/controller/reconciler.go b/runtime/controller/reconciler.go new file mode 100644 index 00000000..65db8a60 --- /dev/null +++ b/runtime/controller/reconciler.go @@ -0,0 +1,150 @@ +/* +Copyright 2025 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "context" + "sync" + + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +// reconcilerWrapper wraps a Reconciler to enhance +// it with additional functionality. +type reconcilerWrapper struct { + reconcile.TypedReconciler[ctrl.Request] + hooks *queueHooks +} + +// interruptContextKey is the type used as a context +// key for storing a context that will be canceled when +// the object associated with a call to Reconcile() is +// requeued. +type interruptContextKey struct{} + +// interruptHandle holds a context that will be canceled +// when the object associated with a call to Reconcile() is +// requeued, and the channel used to deliver the payload +// when that happens. +type interruptHandle struct { + ctx context.Context + payloadCh <-chan *queueEventPayload + payload *queueEventPayload + mu sync.Mutex +} + +func (h *interruptHandle) getPayload() *queueEventPayload { + h.mu.Lock() + defer h.mu.Unlock() + select { + case h.payload = <-h.payloadCh: + default: + } + return h.payload +} + +// WrapReconciler wraps a reconcile.TypedReconciler[ctrl.Request] to +// enhance it with additional functionality. +func WrapReconciler(wrapped reconcile.TypedReconciler[ctrl.Request]) *reconcilerWrapper { + return &reconcilerWrapper{ + TypedReconciler: wrapped, + hooks: newQueueHooks(), + } +} + +// Reconcile implements reconcile.TypedReconciler[ctrl.Request]. +func (r *reconcilerWrapper) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + interruptCtx, cancel, payloadCh := r.ContextWithCancelOnObjectEnqueued(ctx, req) + defer cancel() + + h := &interruptHandle{ + ctx: interruptCtx, + payloadCh: payloadCh, + } + + ctx = context.WithValue(ctx, interruptContextKey{}, h) + return r.TypedReconciler.Reconcile(ctx, req) +} + +// ContextWithCancelOnObjectEnqueued returns a context that will be canceled +// when the specified object is requeued. There's no need to call this method +// directly, as the Reconcile() method already does it for the object being +// reconciled. This method is exposed so that it can be used in tests. +func (r *reconcilerWrapper) ContextWithCancelOnObjectEnqueued(parent context.Context, + obj ctrl.Request) (context.Context, context.CancelFunc, <-chan *queueEventPayload) { + return r.hooks.registerListener(parent, queueEvent{queueEventObjectEnqueued, obj}) +} + +// EnqueueRequestsFromMapFunc wraps a handler.MapFunc to fire off +// hooks for the enqueued objects. +func (r *reconcilerWrapper) EnqueueRequestsFromMapFunc( + objKind string, fn handler.MapFunc) handler.EventHandler { + return handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []ctrl.Request { + reqs := fn(ctx, obj) + payload := queueEventPayload{source: QueueEventSource{ + Kind: objKind, + Name: obj.GetName(), + Namespace: obj.GetNamespace(), + UID: obj.GetUID(), + ResourceVersion: obj.GetResourceVersion(), + }} + for _, req := range reqs { + r.hooks.dispatch(queueEvent{queueEventObjectEnqueued, req}, payload) + } + return reqs + }) +} + +// GetInterruptContext returns a context that will be canceled when +// the object associated with a call to Reconcile() is requeued, or +// the input context itself if this input context is not associated +// with a call to Reconcile(). +func GetInterruptContext(ctx context.Context) context.Context { + if h := getInterruptHandle(ctx); h != nil { + return h.ctx + } + return ctx +} + +func getInterruptHandle(ctx context.Context) *interruptHandle { + if v := ctx.Value(interruptContextKey{}); v != nil { + return v.(*interruptHandle) + } + return nil +} + +// IsObjectEnqueued returns true if the context belongs to a +// Reconcile() call and the object associated with that call +// has been requeued since the reconciliation started. +// If true, it returns also the watched object that caused +// the requeue, otherwise it returns nil. +func IsObjectEnqueued(ctx context.Context) (bool, *QueueEventSource) { + h := getInterruptHandle(ctx) + if h == nil { + return false, nil + } + + p := h.getPayload() + if p == nil { + return false, nil + } + + return true, &p.source +} diff --git a/runtime/controller/reconciler_test.go b/runtime/controller/reconciler_test.go new file mode 100644 index 00000000..94bdb995 --- /dev/null +++ b/runtime/controller/reconciler_test.go @@ -0,0 +1,146 @@ +/* +Copyright 2025 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller_test + +import ( + "context" + "fmt" + "sync/atomic" + "testing" + "time" + + "github.com/fluxcd/pkg/runtime/controller" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/priorityqueue" + "sigs.k8s.io/controller-runtime/pkg/event" +) + +type waitForRequeueReconciler struct { + reconcileCtx atomic.Pointer[context.Context] +} + +func (r *waitForRequeueReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + r.reconcileCtx.Store(&ctx) + select { + case <-controller.GetInterruptContext(ctx).Done(): + case <-time.After(time.Second): + return ctrl.Result{}, fmt.Errorf("timeout after waiting for queue context to be done") + } + is, src := controller.IsObjectEnqueued(ctx) + if !is { + return ctrl.Result{}, fmt.Errorf("expected object to be marked as enqueued") + } + if src == nil || + src.Kind != "ConfigMap" || + src.Name != "test-cm" || + src.Namespace != "default" || + src.UID != "12345" || + src.ResourceVersion != "54321" { + return ctrl.Result{}, fmt.Errorf("expected source to match request") + } + return ctrl.Result{}, nil +} + +func TestReconciler_HooksToObjectEnqueuedEvents(t *testing.T) { + g := NewWithT(t) + + // Wrap the reconciler. + r := &waitForRequeueReconciler{} + reconciler := controller.WrapReconciler(r) + + // Add some garbage to exercise the garbage collection logic. + garbageCtx, garbageCancel, _ := reconciler.ContextWithCancelOnObjectEnqueued( + context.Background(), ctrl.Request{NamespacedName: types.NamespacedName{ + Name: "garbage", + Namespace: "default", + }}) + g.Expect(garbageCtx).ToNot(BeNil()) + g.Expect(garbageCancel).ToNot(BeNil()) + garbageCancel() // Cancel immediately to create garbage. + + // Add also a listener that will be kept during garbage collection. + listenerCtx, listenerCancel, _ := reconciler.ContextWithCancelOnObjectEnqueued( + context.Background(), ctrl.Request{NamespacedName: types.NamespacedName{ + Name: "listener", + Namespace: "default", + }}) + g.Expect(listenerCtx).ToNot(BeNil()) + g.Expect(listenerCancel).ToNot(BeNil()) + defer listenerCancel() // Cancel at the end of the test to avoid garbage collection. + + // Start object reconciliation in a separate goroutine. + obj := ctrl.Request{NamespacedName: types.NamespacedName{ + Name: "test", + Namespace: "default", + }} + errCh := make(chan error, 1) + go func() { + _, err := reconciler.Reconcile(context.Background(), obj) + errCh <- err + close(errCh) + }() + + // Give some time for the Reconcile to start and block on the queue context. + time.Sleep(10 * time.Millisecond) + + // Check that the object is not enqueued yet. + g.Eventually(func() bool { return r.reconcileCtx.Load() != nil }, time.Second).To(BeTrue()) + is, _ := controller.IsObjectEnqueued(*r.reconcileCtx.Load()) + g.Expect(is).To(BeFalse()) + + // Simulate object being enqueued. + reconciler.EnqueueRequestsFromMapFunc("ConfigMap", func(context.Context, client.Object) []ctrl.Request { + return []ctrl.Request{obj} + }).Create(context.Background(), event.CreateEvent{ + Object: &corev1.ConfigMap{ + ObjectMeta: ctrl.ObjectMeta{ + Name: "test-cm", + Namespace: "default", + UID: "12345", + ResourceVersion: "54321", + }, + }, + }, priorityqueue.New[ctrl.Request]("test")) + + // Wait for the Reconcile to finish and check the result. + select { + case err := <-errCh: + g.Expect(err).ToNot(HaveOccurred()) + case <-time.After(time.Second): + t.Fatal("timeout waiting for Reconcile to finish") + } +} + +func TestGetObjectContext(t *testing.T) { + t.Run("returns the same context when called outside Reconcile", func(t *testing.T) { + g := NewWithT(t) + ctx := context.Background() + g.Expect(controller.GetInterruptContext(ctx)).To(Equal(ctx)) + }) +} + +func TestIsObjectEnqueued(t *testing.T) { + t.Run("returns false when called outside Reconcile", func(t *testing.T) { + g := NewWithT(t) + ctx := context.Background() + g.Expect(controller.IsObjectEnqueued(ctx)).To(BeFalse()) + }) +} diff --git a/runtime/go.mod b/runtime/go.mod index f5fe493d..754eb933 100644 --- a/runtime/go.mod +++ b/runtime/go.mod @@ -15,7 +15,7 @@ require ( github.com/fluxcd/pkg/apis/acl v0.9.0 github.com/fluxcd/pkg/apis/event v0.20.0 github.com/fluxcd/pkg/apis/kustomize v1.13.0 - github.com/fluxcd/pkg/apis/meta v1.22.0 + github.com/fluxcd/pkg/apis/meta v1.23.0 github.com/go-logr/logr v1.4.3 github.com/go-logr/zapr v1.3.0 github.com/google/cel-go v0.26.1 diff --git a/tests/integration/go.mod b/tests/integration/go.mod index d1068b41..cc7ac1ce 100644 --- a/tests/integration/go.mod +++ b/tests/integration/go.mod @@ -17,12 +17,12 @@ replace ( require ( github.com/elazarl/goproxy v1.7.2 github.com/fluxcd/cli-utils v0.36.0-flux.15 - github.com/fluxcd/pkg/apis/meta v1.22.0 - github.com/fluxcd/pkg/auth v0.32.0 + github.com/fluxcd/pkg/apis/meta v1.23.0 + github.com/fluxcd/pkg/auth v0.33.0 github.com/fluxcd/pkg/cache v0.12.0 github.com/fluxcd/pkg/git v0.37.0 github.com/fluxcd/pkg/git/gogit v0.41.0 - github.com/fluxcd/pkg/runtime v0.89.0 + github.com/fluxcd/pkg/runtime v0.90.0 github.com/fluxcd/test-infra/tftestenv v0.0.0-20250626232827-e0ca9c3f8d7b github.com/go-git/go-git/v5 v5.16.3 github.com/google/go-containerregistry v0.20.6