diff --git a/cmd/core/main.go b/cmd/core/main.go index 8df32937a..d27f5ac45 100644 --- a/cmd/core/main.go +++ b/cmd/core/main.go @@ -127,7 +127,10 @@ func main() { setupLog.Info(fmt.Sprintf("KubeVela Version: %s, GIT Revision: %s.", version.VelaVersion, version.GitRevision)) setupLog.Info(fmt.Sprintf("Disable Capabilities: %s.", disableCaps)) - mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ + restConfig := ctrl.GetConfigOrDie() + restConfig.UserAgent = kubevelaName + "/" + version.GitRevision + + mgr, err := ctrl.NewManager(restConfig, ctrl.Options{ Scheme: scheme, MetricsBindAddress: metricsAddr, LeaderElection: enableLeaderElection, diff --git a/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/applicationconfiguration.go b/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/applicationconfiguration.go index 3b2d588eb..99dc4a9c9 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/applicationconfiguration.go +++ b/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/applicationconfiguration.go @@ -102,7 +102,7 @@ func Setup(mgr ctrl.Manager, args core.Args, l logging.Logger) error { CustomRevisionHookURL: args.CustomRevisionHookURL, }). Complete(NewReconciler(mgr, dm, - WithLogger(l.WithValues("controller", name)), + l.WithValues("controller", name), WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))), WithApplyOnceOnlyMode(args.ApplyMode))) } @@ -148,13 +148,6 @@ func WithGarbageCollector(gc GarbageCollector) ReconcilerOption { } } -// WithLogger specifies how the Reconciler should log messages. -func WithLogger(l logging.Logger) ReconcilerOption { - return func(r *OAMApplicationReconciler) { - r.log = l - } -} - // WithRecorder specifies how the Reconciler should record events. func WithRecorder(er event.Recorder) ReconcilerOption { return func(r *OAMApplicationReconciler) { @@ -186,7 +179,7 @@ func WithApplyOnceOnlyMode(mode core.ApplyOnceOnlyMode) ReconcilerOption { // NewReconciler returns an OAMApplicationReconciler that reconciles ApplicationConfigurations // by rendering and instantiating their Components and Traits. -func NewReconciler(m ctrl.Manager, dm discoverymapper.DiscoveryMapper, o ...ReconcilerOption) *OAMApplicationReconciler { +func NewReconciler(m ctrl.Manager, dm discoverymapper.DiscoveryMapper, log logging.Logger, o ...ReconcilerOption) *OAMApplicationReconciler { r := &OAMApplicationReconciler{ client: m.GetClient(), scheme: m.GetScheme(), @@ -198,12 +191,12 @@ func NewReconciler(m ctrl.Manager, dm discoverymapper.DiscoveryMapper, o ...Reco trait: ResourceRenderFn(renderTrait), }, workloads: &workloads{ - applicator: apply.NewAPIApplicator(m.GetClient()), + applicator: apply.NewAPIApplicator(m.GetClient(), log), rawClient: m.GetClient(), dm: dm, }, gc: GarbageCollectorFn(eligible), - log: logging.NewNopLogger(), + log: log, record: event.NewNopRecorder(), preHooks: make(map[string]ControllerHooks), postHooks: make(map[string]ControllerHooks), @@ -299,7 +292,7 @@ func (r *OAMApplicationReconciler) Reconcile(req reconcile.Request) (result reco log.Debug("Successfully rendered components", "workloads", len(workloads)) r.record.Event(ac, event.Normal(reasonRenderComponents, "Successfully rendered components", "workloads", strconv.Itoa(len(workloads)))) - applyOpts := []apply.ApplyOption{apply.MustBeControllableBy(ac.GetUID()), applyOnceOnly(ac, r.applyOnceOnlyMode)} + applyOpts := []apply.ApplyOption{apply.MustBeControllableBy(ac.GetUID()), applyOnceOnly(ac, r.applyOnceOnlyMode, log)} if err := r.workloads.Apply(ctx, ac.Status.Workloads, workloads, applyOpts...); err != nil { log.Debug("Cannot apply components", "error", err, "requeue-after", time.Now().Add(shortWait)) r.record.Event(ac, event.Warning(reasonCannotApplyComponents, err)) @@ -361,7 +354,6 @@ func (r *OAMApplicationReconciler) updateStatus(ctx context.Context, ac, acPatch historyWorkloads := make([]v1alpha2.HistoryWorkload, 0) for i, w := range workloads { ac.Status.Workloads[i] = workloads[i].Status() - ac.Status.Workloads[i].ObservedGeneration = ac.GetGeneration() if !w.RevisionEnabled { continue } @@ -398,6 +390,12 @@ func updateObservedGeneration(ac *v1alpha2.ApplicationConfiguration) { if ac.Status.ObservedGeneration != ac.Generation { ac.Status.ObservedGeneration = ac.Generation } + for i, w := range ac.Status.Workloads { + // only all workload meet requirements can say the generation is observed successfully + if w.ObservedGeneration != ac.Generation && len(ac.Status.Dependency.Unsatisfied) == 0 { + ac.Status.Workloads[i].ObservedGeneration = ac.GetGeneration() + } + } } func patchExtraStatusField(acStatus *v1alpha2.ApplicationConfigurationStatus, acPatchStatus v1alpha2.ApplicationConfigurationStatus) { @@ -591,12 +589,11 @@ func (e *GenerationUnchanged) Error() string { // applyOnceOnly is an ApplyOption that controls the applying mechanism for workload and trait. // More detail refers to the ApplyOnceOnlyMode type annotation -func applyOnceOnly(ac *v1alpha2.ApplicationConfiguration, mode core.ApplyOnceOnlyMode) apply.ApplyOption { +func applyOnceOnly(ac *v1alpha2.ApplicationConfiguration, mode core.ApplyOnceOnlyMode, log logging.Logger) apply.ApplyOption { return func(_ context.Context, existing, desired runtime.Object) error { if mode == core.ApplyOnceOnlyOff { return nil } - d, _ := desired.(metav1.Object) if d == nil { return errors.Errorf("cannot access metadata of object being applied: %q", @@ -608,6 +605,7 @@ func applyOnceOnly(ac *v1alpha2.ApplicationConfiguration, mode core.ApplyOnceOnl dLabels[oam.LabelOAMResourceType] != oam.ResourceTypeTrait { // this ApplyOption only works for workload and trait // skip if the resource is not workload nor trait, e.g., scope + log.Info("ignore apply only once check, because resourceType is not workload or trait", oam.LabelOAMResourceType, dLabels[oam.LabelOAMResourceType]) return nil } @@ -615,6 +613,7 @@ func applyOnceOnly(ac *v1alpha2.ApplicationConfiguration, mode core.ApplyOnceOnl if existing == nil { if mode != core.ApplyOnceOnlyForce { // non-force mode will always create the resource if not exist. + log.Info("apply only once with mode:" + string(mode) + ", but old resource not exist, will create a new one") return nil } @@ -638,11 +637,14 @@ func applyOnceOnly(ac *v1alpha2.ApplicationConfiguration, mode core.ApplyOnceOnl } // don't use if-else here because it will miss the case that the resource is a trait if createdBefore { - // the resource was created before and appconfig status recorded the resource version applied - // if recored ObservedGeneration and ComponentRevisionName both equal to the applied resource's, + // the resource was created before and appConfig status recorded the resource version applied + // if recorded ObservedGeneration and ComponentRevisionName both equal to the applied resource's, // that means its spec is not changed if (strconv.Itoa(int(w.ObservedGeneration)) != dAnnots[oam.AnnotationAppGeneration]) || (w.ComponentRevisionName != dLabels[oam.LabelAppComponentRevision]) { + log.Info("apply only once with mode: "+string(mode)+", but condition not meet, will create new", + oam.AnnotationAppGeneration, strconv.Itoa(int(w.ObservedGeneration))+"/"+dAnnots[oam.AnnotationAppGeneration], + oam.LabelAppComponentRevision, w.ComponentRevisionName+"/"+dLabels[oam.LabelAppComponentRevision]) // its spec is changed, so re-create the resource return nil } @@ -650,6 +652,7 @@ func applyOnceOnly(ac *v1alpha2.ApplicationConfiguration, mode core.ApplyOnceOnl return &GenerationUnchanged{} } } + log.Info("apply only once with mode: force, but resource not created before, will create new", "appConfig", ac.Name) // no recorded workloads nor traits matches the applied resource // that means the resource is not created before, so create it return nil @@ -662,10 +665,13 @@ func applyOnceOnly(ac *v1alpha2.ApplicationConfiguration, mode core.ApplyOnceOnl existing.GetObjectKind().GroupVersionKind()) } eLabels := e.GetLabels() - // if existing reource's (observed)AppConfigGeneration and ComponentRevisionName both equal to the applied one's, + // if existing resource's (observed)AppConfigGeneration and ComponentRevisionName both equal to the applied one's, // that means its spec is not changed if (e.GetAnnotations()[oam.AnnotationAppGeneration] != dAnnots[oam.AnnotationAppGeneration]) || (eLabels[oam.LabelAppComponentRevision] != dLabels[oam.LabelAppComponentRevision]) { + log.Info("apply only once with mode: "+string(mode)+", but new generation or revision created, will create new", + oam.AnnotationAppGeneration, e.GetAnnotations()[oam.AnnotationAppGeneration]+"/"+dAnnots[oam.AnnotationAppGeneration], + oam.LabelAppComponentRevision, eLabels[oam.LabelAppComponentRevision]+"/"+dLabels[oam.LabelAppComponentRevision]) // its spec is changed, so apply new configuration to it return nil } diff --git a/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/applicationconfiguration_test.go b/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/applicationconfiguration_test.go index 7cf31bf1e..98ac293fb 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/applicationconfiguration_test.go +++ b/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/applicationconfiguration_test.go @@ -661,7 +661,7 @@ func TestReconciler(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { - r := NewReconciler(tc.args.m, nil, tc.args.o...) + r := NewReconciler(tc.args.m, nil, logging.NewNopLogger(), tc.args.o...) got, err := r.Reconcile(reconcile.Request{}) if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" { @@ -1628,7 +1628,7 @@ func TestUpdateStatus(t *testing.T) { }, } - r := NewReconciler(m, nil) + r := NewReconciler(m, nil, logging.NewNopLogger()) ac := &v1alpha2.ApplicationConfiguration{} err := r.client.Get(context.Background(), types.NamespacedName{Name: "example-appconfig"}, ac) diff --git a/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/apply_once_only_test.go b/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/apply_once_only_test.go index 05d05147f..3a38ce8f7 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/apply_once_only_test.go +++ b/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/apply_once_only_test.go @@ -4,6 +4,8 @@ import ( "context" "time" + "k8s.io/apimachinery/pkg/types" + . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -285,6 +287,77 @@ var _ = Describe("Test apply (workloads/traits) once only", func() { }) When("ApplyOnceOnlyForce is enabled", func() { + It("should normally create workload/trait resources at fist time", func() { + By("Enable ApplyOnceOnlyForce") + reconciler.applyOnceOnlyMode = core.ApplyOnceOnlyForce + component2 := v1alpha2.Component{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "core.oam.dev/v1alpha2", + Kind: "Component", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "mycomp2", + Namespace: namespace, + }, + Spec: v1alpha2.ComponentSpec{ + Workload: runtime.RawExtension{ + Object: &cw, + }, + }, + } + newFakeTrait := fakeTrait.DeepCopy() + newFakeTrait.SetName("mytrait2") + appConfig2 := v1alpha2.ApplicationConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "myac2", + Namespace: namespace, + }, + Spec: v1alpha2.ApplicationConfigurationSpec{ + Components: []v1alpha2.ApplicationConfigurationComponent{ + { + ComponentName: "mycomp2", + Traits: []v1alpha2.ComponentTrait{ + {Trait: runtime.RawExtension{Object: newFakeTrait}}, + }, + }, + }, + }, + } + + By("Create Component") + Expect(k8sClient.Create(ctx, &component2)).Should(Succeed()) + time.Sleep(time.Second) + + By("Creat appConfig & check successfully") + Expect(k8sClient.Create(ctx, &appConfig2)).Should(Succeed()) + time.Sleep(time.Second) + + By("Reconcile") + Expect(func() error { + _, err := reconciler.Reconcile(reconcile.Request{NamespacedName: types.NamespacedName{Name: "myac2", Namespace: namespace}}) + return err + }()).Should(BeNil()) + time.Sleep(2 * time.Second) + + By("Get workload instance & Check workload spec") + cwObj := v1alpha2.ContainerizedWorkload{} + Eventually(func() error { + return k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: "mycomp2"}, &cwObj) + }, 5*time.Second, time.Second).Should(BeNil()) + Expect(cwObj.Spec.Containers[0].Image).Should(Equal(image1)) + + By("Get trait instance & Check trait spec") + fooObj := &unstructured.Unstructured{} + fooObj.SetAPIVersion("example.com/v1") + fooObj.SetKind("Foo") + Eventually(func() error { + return k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: "mytrait2"}, fooObj) + }, 3*time.Second, time.Second).Should(BeNil()) + fooObjV, _, _ := unstructured.NestedString(fooObj.Object, "spec", "key") + Expect(fooObjV).Should(Equal(traitSpecValue1)) + + }) + It("should not revert changes of workload/trait made by others", func() { By("Enable ApplyOnceOnlyForce") reconciler.applyOnceOnlyMode = core.ApplyOnceOnlyForce @@ -426,7 +499,7 @@ var _ = Describe("Test apply (workloads/traits) once only", func() { recreatedFooObj.SetKind("Foo") Expect(k8sClient.Get(ctx, traitObjKey, &recreatedFooObj)).Should(util.NotFoundMatcher{}) - By("Update Appconfig to trigger generation augment") + By("Update AppConfig to trigger generation updated") unstructured.SetNestedField(fakeTrait.Object, "newvalue", "spec", "key") appConfig = v1alpha2.ApplicationConfiguration{ ObjectMeta: metav1.ObjectMeta{ @@ -445,10 +518,11 @@ var _ = Describe("Test apply (workloads/traits) once only", func() { }, } Expect(k8sClient.Patch(ctx, &appConfig, client.Merge)).Should(Succeed()) + time.Sleep(1 * time.Second) By("Reconcile") reconcileRetry(reconciler, req) - time.Sleep(3 * time.Second) + time.Sleep(2 * time.Second) By("Check workload is re-created by reconciliation") recreatedCwObj = v1alpha2.ContainerizedWorkload{} diff --git a/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/suite_test.go b/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/suite_test.go index ad5a1ca97..480a3ded6 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/suite_test.go +++ b/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/suite_test.go @@ -150,7 +150,7 @@ var _ = BeforeSuite(func(done Done) { }, time.Second*30, time.Millisecond*500).Should(BeNil()) Expect(mapping.Resource.Resource).Should(Equal("foo")) - reconciler = NewReconciler(mgr, dm, WithLogger(logging.NewLogrLogger(ctrl.Log.WithName("suit-test-appconfig")))) + reconciler = NewReconciler(mgr, dm, logging.NewLogrLogger(ctrl.Log.WithName("suit-test-appconfig"))) componentHandler = &ComponentHandler{Client: k8sClient, RevisionLimit: 100, Logger: logging.NewLogrLogger(ctrl.Log.WithName("component-handler"))} By("Creating workload definition and trait definition") diff --git a/pkg/utils/apply/apply.go b/pkg/utils/apply/apply.go index 2d68e32f4..a0b4a671b 100644 --- a/pkg/utils/apply/apply.go +++ b/pkg/utils/apply/apply.go @@ -3,22 +3,23 @@ package apply import ( "context" + "github.com/crossplane/crossplane-runtime/pkg/logging" + "github.com/pkg/errors" kerrors "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/runtime" "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" "github.com/oam-dev/kubevela/pkg/oam" ) // Applicator applies new state to an object or create it if not exist. -// It employes the same mechanism as `kubectl apply`, that is, for each resource being applied, +// It uses the same mechanism as `kubectl apply`, that is, for each resource being applied, // computing a three-way diff merge in client side based on its current state, modified stated, -// and last-applied-state which is tracked through an specific annotaion. +// and last-applied-state which is tracked through an specific annotation. // If the resource doesn't exist before, Apply will create it. type Applicator interface { Apply(context.Context, runtime.Object, ...ApplyOption) error @@ -32,20 +33,23 @@ type ApplyOption func(ctx context.Context, existing, desired runtime.Object) err // NewAPIApplicator creates an Applicator that applies state to an // object or creates the object if not exist. -func NewAPIApplicator(c client.Client) *APIApplicator { +func NewAPIApplicator(c client.Client, log logging.Logger) *APIApplicator { return &APIApplicator{ - creatorFn(createOrGetExisting), - patcherFn(threeWayMergePatch), c} + creator: creatorFn(createOrGetExisting), + patcher: patcherFn(threeWayMergePatch), + c: c, + log: log, + } } type creator interface { - createOrGetExisting(context.Context, client.Client, runtime.Object, ...ApplyOption) (runtime.Object, error) + createOrGetExisting(context.Context, logging.Logger, client.Client, runtime.Object, ...ApplyOption) (runtime.Object, error) } -type creatorFn func(context.Context, client.Client, runtime.Object, ...ApplyOption) (runtime.Object, error) +type creatorFn func(context.Context, logging.Logger, client.Client, runtime.Object, ...ApplyOption) (runtime.Object, error) -func (fn creatorFn) createOrGetExisting(ctx context.Context, c client.Client, o runtime.Object, ao ...ApplyOption) (runtime.Object, error) { - return fn(ctx, c, o, ao...) +func (fn creatorFn) createOrGetExisting(ctx context.Context, log logging.Logger, c client.Client, o runtime.Object, ao ...ApplyOption) (runtime.Object, error) { + return fn(ctx, log, c, o, ao...) } type patcher interface { @@ -62,12 +66,23 @@ func (fn patcherFn) patch(c, m runtime.Object) (client.Patch, error) { type APIApplicator struct { creator patcher - c client.Client + c client.Client + log logging.Logger +} + +// loggingApply will record a log with desired object applied +func loggingApply(log logging.Logger, msg string, desired runtime.Object) { + d, ok := desired.(metav1.Object) + if !ok { + log.Debug(msg, "resource", desired.GetObjectKind().GroupVersionKind().String()) + return + } + log.Debug(msg, "name", d.GetName(), "resource", desired.GetObjectKind().GroupVersionKind().String()) } // Apply applies new state to an object or create it if not exist func (a *APIApplicator) Apply(ctx context.Context, desired runtime.Object, ao ...ApplyOption) error { - existing, err := a.createOrGetExisting(ctx, a.c, desired, ao...) + existing, err := a.createOrGetExisting(ctx, a.log, a.c, desired, ao...) if err != nil { return err } @@ -79,6 +94,7 @@ func (a *APIApplicator) Apply(ctx context.Context, desired runtime.Object, ao .. if err := executeApplyOptions(ctx, existing, desired, ao); err != nil { return err } + loggingApply(a.log, "patching object", desired) patch, err := a.patcher.patch(existing, desired) if err != nil { return errors.Wrap(err, "cannot calculate patch by computing a three way diff") @@ -88,7 +104,7 @@ func (a *APIApplicator) Apply(ctx context.Context, desired runtime.Object, ao .. // createOrGetExisting will create the object if it does not exist // or get and return the existing object -func createOrGetExisting(ctx context.Context, c client.Client, desired runtime.Object, ao ...ApplyOption) (runtime.Object, error) { +func createOrGetExisting(ctx context.Context, log logging.Logger, c client.Client, desired runtime.Object, ao ...ApplyOption) (runtime.Object, error) { m, ok := desired.(oam.Object) if !ok { return nil, errors.New("cannot access object metadata") @@ -102,6 +118,7 @@ func createOrGetExisting(ctx context.Context, c client.Client, desired runtime.O if err := addLastAppliedConfigAnnotation(desired); err != nil { return nil, err } + loggingApply(log, "creating object", desired) return nil, errors.Wrap(c.Create(ctx, desired), "cannot create object") } diff --git a/pkg/utils/apply/apply_suite_test.go b/pkg/utils/apply/apply_suite_test.go index af85df3a6..4daf8d283 100644 --- a/pkg/utils/apply/apply_suite_test.go +++ b/pkg/utils/apply/apply_suite_test.go @@ -11,6 +11,7 @@ import ( oamcore "github.com/oam-dev/kubevela/apis/core.oam.dev" oamstd "github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1" + "github.com/crossplane/crossplane-runtime/pkg/logging" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -56,7 +57,7 @@ var _ = BeforeSuite(func(done Done) { rawClient, err = client.New(cfg, client.Options{Scheme: testScheme}) Expect(err).ShouldNot(HaveOccurred()) Expect(rawClient).ShouldNot(BeNil()) - k8sApplicator = NewAPIApplicator(rawClient) + k8sApplicator = NewAPIApplicator(rawClient, logging.NewNopLogger()) By("Create test namespace") applyNS = corev1.Namespace{ diff --git a/pkg/utils/apply/apply_test.go b/pkg/utils/apply/apply_test.go index 26e83bd8d..f27902c7c 100644 --- a/pkg/utils/apply/apply_test.go +++ b/pkg/utils/apply/apply_test.go @@ -4,6 +4,7 @@ import ( "context" "testing" + "github.com/crossplane/crossplane-runtime/pkg/logging" "github.com/crossplane/crossplane-runtime/pkg/test" "github.com/google/go-cmp/cmp" "github.com/pkg/errors" @@ -117,13 +118,14 @@ func TestAPIApplicator(t *testing.T) { for caseName, tc := range cases { t.Run(caseName, func(t *testing.T) { a := &APIApplicator{ - creator: creatorFn(func(_ context.Context, _ client.Client, _ runtime.Object, _ ...ApplyOption) (runtime.Object, error) { + creator: creatorFn(func(_ context.Context, _ logging.Logger, _ client.Client, _ runtime.Object, _ ...ApplyOption) (runtime.Object, error) { return tc.args.existing, tc.args.creatorErr }), patcher: patcherFn(func(c, m runtime.Object) (client.Patch, error) { return nil, tc.args.patcherErr }), - c: tc.c, + c: tc.c, + log: logging.NewNopLogger(), } result := a.Apply(ctx, tc.args.desired, tc.args.ao...) if diff := cmp.Diff(tc.want, result, test.EquateErrors()); diff != "" { @@ -284,7 +286,7 @@ func TestCreator(t *testing.T) { for caseName, tc := range cases { t.Run(caseName, func(t *testing.T) { - result, err := createOrGetExisting(ctx, tc.c, tc.args.desired, tc.args.ao...) + result, err := createOrGetExisting(ctx, logging.NewNopLogger(), tc.c, tc.args.desired, tc.args.ao...) if diff := cmp.Diff(tc.want.existing, result); diff != "" { t.Errorf("\n%s\ncreateOrGetExisting(...): -want , +got \n%s\n", tc.reason, diff) }