diff --git a/examples/source-to-knative-service/developer/serviceaccount.yaml b/examples/source-to-knative-service/developer/serviceaccount.yaml index 104e7a3d6..1258b5dfc 100644 --- a/examples/source-to-knative-service/developer/serviceaccount.yaml +++ b/examples/source-to-knative-service/developer/serviceaccount.yaml @@ -62,3 +62,4 @@ rules: - delete - patch - watch + - get diff --git a/pkg/realizer/deliverable/component.go b/pkg/realizer/deliverable/component.go index e590c3929..ad9a2367e 100644 --- a/pkg/realizer/deliverable/component.go +++ b/pkg/realizer/deliverable/component.go @@ -115,7 +115,7 @@ func (r *resourceRealizer) Do(ctx context.Context, resource *v1alpha1.DeliveryRe } } - err = r.deliverableRepo.EnsureObjectExistsOnCluster(ctx, stampedObject, true) + err = r.deliverableRepo.EnsureMutableObjectExistsOnCluster(ctx, stampedObject) if err != nil { log.Error(err, "failed to ensure object exists on cluster", "object", stampedObject) return nil, nil, ApplyStampedObjectError{ diff --git a/pkg/realizer/deliverable/component_test.go b/pkg/realizer/deliverable/component_test.go index 871bc0d1e..ca9528386 100644 --- a/pkg/realizer/deliverable/component_test.go +++ b/pkg/realizer/deliverable/component_test.go @@ -159,17 +159,16 @@ var _ = Describe("Resource", func() { } fakeSystemRepo.GetDeliveryTemplateReturns(templateAPI, nil) - fakeDeliverableRepo.EnsureObjectExistsOnClusterReturns(nil) + fakeDeliverableRepo.EnsureMutableObjectExistsOnClusterReturns(nil) }) It("creates a stamped object and returns the outputs and stampedObjects", func() { returnedStampedObject, out, err := r.Do(ctx, &resource, deliveryName, outputs) Expect(err).ToNot(HaveOccurred()) - _, stampedObject, allowUpdate := fakeDeliverableRepo.EnsureObjectExistsOnClusterArgsForCall(0) + _, stampedObject := fakeDeliverableRepo.EnsureMutableObjectExistsOnClusterArgsForCall(0) Expect(returnedStampedObject).To(Equal(stampedObject)) - Expect(allowUpdate).To(BeTrue()) metadata := stampedObject.Object["metadata"] metadataValues, ok := metadata.(map[string]interface{}) @@ -186,6 +185,7 @@ var _ = Describe("Resource", func() { "blockOwnerDeletion": true, }, })) + Expect(stampedObject.Object["data"]).To(Equal(map[string]interface{}{"player_current_lives": "some-url", "some_other_info": "some-revision"})) Expect(metadataValues["labels"]).To(Equal(map[string]interface{}{ "carto.run/cluster-delivery-name": "delivery-name", "carto.run/resource-name": "resource-1", @@ -194,7 +194,6 @@ var _ = Describe("Resource", func() { "carto.run/deliverable-namespace": "", "carto.run/template-kind": "ClusterSourceTemplate", })) - Expect(stampedObject.Object["data"]).To(Equal(map[string]interface{}{"player_current_lives": "some-url", "some_other_info": "some-revision"})) Expect(out.Source.Revision).To(Equal("some-revision")) Expect(out.Source.URL).To(Equal("some-url")) @@ -305,7 +304,7 @@ var _ = Describe("Resource", func() { } fakeSystemRepo.GetDeliveryTemplateReturns(templateAPI, nil) - fakeDeliverableRepo.EnsureObjectExistsOnClusterReturns(nil) + fakeDeliverableRepo.EnsureMutableObjectExistsOnClusterReturns(nil) }) It("returns RetrieveOutputError", func() { @@ -316,7 +315,7 @@ var _ = Describe("Resource", func() { }) }) - When("unable to EnsureObjectExistsOnCluster the stamped object", func() { + When("unable to EnsureImmutableObjectExistsOnCluster the stamped object", func() { BeforeEach(func() { resource.Sources = []v1alpha1.ResourceReference{ { @@ -366,7 +365,7 @@ var _ = Describe("Resource", func() { } fakeSystemRepo.GetDeliveryTemplateReturns(templateAPI, nil) - fakeDeliverableRepo.EnsureObjectExistsOnClusterReturns(errors.New("bad object")) + fakeDeliverableRepo.EnsureMutableObjectExistsOnClusterReturns(errors.New("bad object")) }) It("returns ApplyStampedObjectError", func() { diff --git a/pkg/realizer/runnable/realizer.go b/pkg/realizer/runnable/realizer.go index 750808be7..8b107625b 100644 --- a/pkg/realizer/runnable/realizer.go +++ b/pkg/realizer/runnable/realizer.go @@ -96,7 +96,7 @@ func (p *runnableRealizer) Realize(ctx context.Context, runnable *v1alpha1.Runna } // FIXME: why are we taking a DeepCopy? - err = runnableRepo.EnsureObjectExistsOnCluster(ctx, stampedObject.DeepCopy(), false) + err = runnableRepo.EnsureImmutableObjectExistsOnCluster(ctx, stampedObject.DeepCopy(), map[string]string{"carto.run/runnable-name": runnable.Name}) if err != nil { log.Error(err, "failed to ensure object exists on cluster", "object", stampedObject) return nil, nil, ApplyStampedObjectError{ @@ -105,16 +105,13 @@ func (p *runnableRealizer) Realize(ctx context.Context, runnable *v1alpha1.Runna } } - objectForListCall := stampedObject.DeepCopy() - objectForListCall.SetLabels(labels) - - allRunnableStampedObjects, err := runnableRepo.ListUnstructured(ctx, objectForListCall) + allRunnableStampedObjects, err := runnableRepo.ListUnstructured(ctx, stampedObject.GroupVersionKind(), stampedObject.GetNamespace(), labels) if err != nil { log.Error(err, "failed to list objects") return stampedObject, nil, ListCreatedObjectsError{ Err: err, - Namespace: objectForListCall.GetNamespace(), - Labels: objectForListCall.GetLabels(), + Namespace: stampedObject.GetNamespace(), + Labels: labels, } } @@ -146,12 +143,8 @@ func resolveSelector(ctx context.Context, selector *v1alpha1.ResourceSelector, r if selector == nil { return nil, nil } - queryObj := &unstructured.Unstructured{} - queryObj.SetGroupVersionKind(schema.FromAPIVersionAndKind(selector.Resource.APIVersion, selector.Resource.Kind)) - queryObj.SetLabels(selector.MatchingLabels) - queryObj.SetNamespace(namespace) - results, err := repository.ListUnstructured(ctx, queryObj) + results, err := repository.ListUnstructured(ctx, schema.FromAPIVersionAndKind(selector.Resource.APIVersion, selector.Resource.Kind), namespace, selector.MatchingLabels) if err != nil { log.Error(err, "failed to list objects matching selector", "selector", selector.MatchingLabels) return nil, fmt.Errorf("failed to list objects matching selector [%+v]: %w", selector.MatchingLabels, err) @@ -174,7 +167,7 @@ func resolveClusterScopedSelector(ctx context.Context, selector *v1alpha1.Resour queryObj.SetGroupVersionKind(schema.FromAPIVersionAndKind(selector.Resource.APIVersion, selector.Resource.Kind)) queryObj.SetLabels(selector.MatchingLabels) - results, err := repository.ListUnstructured(ctx, queryObj) + results, err := repository.ListUnstructured(ctx, schema.FromAPIVersionAndKind(selector.Resource.APIVersion, selector.Resource.Kind), "", selector.MatchingLabels) if err != nil { log.Error(err, "failed to list objects matching selector", "selector", selector.MatchingLabels) return nil, fmt.Errorf("failed to list objects matching selector [%+v]: %w", selector.MatchingLabels, err) diff --git a/pkg/realizer/runnable/realizer_test.go b/pkg/realizer/runnable/realizer_test.go index b27475c90..79bdbfdaa 100644 --- a/pkg/realizer/runnable/realizer_test.go +++ b/pkg/realizer/runnable/realizer_test.go @@ -108,7 +108,7 @@ var _ = Describe("Realizer", func() { createdUnstructured = &unstructured.Unstructured{} - runnableRepo.EnsureObjectExistsOnClusterStub = func(ctx context.Context, obj *unstructured.Unstructured, allowUpdate bool) error { + runnableRepo.EnsureImmutableObjectExistsOnClusterStub = func(ctx context.Context, obj *unstructured.Unstructured, labels map[string]string) error { createdUnstructured.Object = obj.Object return nil } @@ -128,9 +128,8 @@ var _ = Describe("Realizer", func() { }, )) - Expect(runnableRepo.EnsureObjectExistsOnClusterCallCount()).To(Equal(1)) - _, stamped, allowUpdate := runnableRepo.EnsureObjectExistsOnClusterArgsForCall(0) - Expect(allowUpdate).To(BeFalse()) + Expect(runnableRepo.EnsureImmutableObjectExistsOnClusterCallCount()).To(Equal(1)) + _, stamped, labels := runnableRepo.EnsureImmutableObjectExistsOnClusterArgsForCall(0) Expect(stamped.Object).To( MatchKeys(IgnoreExtras, Keys{ "metadata": MatchKeys(IgnoreExtras, Keys{ @@ -143,6 +142,9 @@ var _ = Describe("Realizer", func() { }), }), ) + Expect(labels).To(Equal(map[string]string{ + "carto.run/runnable-name": "my-runnable", + })) }) It("does not return an error", func() { @@ -165,9 +167,9 @@ var _ = Describe("Realizer", func() { Expect(stampedObject.Object["kind"]).To(Equal("TestObj")) }) - Context("error on EnsureObjectExistsOnCluster", func() { + Context("error on EnsureImmutableObjectExistsOnCluster", func() { BeforeEach(func() { - runnableRepo.EnsureObjectExistsOnClusterReturns(errors.New("some bad error")) + runnableRepo.EnsureImmutableObjectExistsOnClusterReturns(errors.New("some bad error")) }) It("returns ApplyStampedObjectError", func() { @@ -207,15 +209,15 @@ var _ = Describe("Realizer", func() { _, _, _ = rlzr.Realize(ctx, runnable, systemRepo, runnableRepo) Expect(runnableRepo.ListUnstructuredCallCount()).To(Equal(2)) - _, clientQueryObjectForSelector := runnableRepo.ListUnstructuredArgsForCall(0) + _, gvk, namespace, labels := runnableRepo.ListUnstructuredArgsForCall(0) - Expect(clientQueryObjectForSelector.GetAPIVersion()).To(Equal("apiversion-to-be-selected")) - Expect(clientQueryObjectForSelector.GetKind()).To(Equal("kind-to-be-selected")) - Expect(clientQueryObjectForSelector.GetLabels()).To(Equal(map[string]string{"expected-label": "expected-value"})) + Expect(gvk.Version).To(Equal("apiversion-to-be-selected")) + Expect(gvk.Kind).To(Equal("kind-to-be-selected")) + Expect(labels).To(Equal(map[string]string{"expected-label": "expected-value"})) + Expect(namespace).To(Equal("my-important-ns")) - Expect(runnableRepo.EnsureObjectExistsOnClusterCallCount()).To(Equal(1)) - _, stamped, allowUpdate := runnableRepo.EnsureObjectExistsOnClusterArgsForCall(0) - Expect(allowUpdate).To(BeFalse()) + Expect(runnableRepo.EnsureImmutableObjectExistsOnClusterCallCount()).To(Equal(1)) + _, stamped, labels := runnableRepo.EnsureImmutableObjectExistsOnClusterArgsForCall(0) Expect(stamped.Object).To( MatchKeys(IgnoreExtras, Keys{ "metadata": MatchKeys(IgnoreExtras, Keys{ @@ -230,6 +232,9 @@ var _ = Describe("Realizer", func() { }), }), ) + Expect(labels).To(Equal(map[string]string{ + "carto.run/runnable-name": "my-runnable", + })) }) }) @@ -317,7 +322,7 @@ var _ = Describe("Realizer", func() { createdUnstructured = &unstructured.Unstructured{} - runnableRepo.EnsureObjectExistsOnClusterStub = func(ctx context.Context, obj *unstructured.Unstructured, allowUpdate bool) error { + runnableRepo.EnsureImmutableObjectExistsOnClusterStub = func(ctx context.Context, obj *unstructured.Unstructured, labels map[string]string) error { createdUnstructured.Object = obj.Object return nil } diff --git a/pkg/realizer/workload/component.go b/pkg/realizer/workload/component.go index f1683d002..af70a6539 100644 --- a/pkg/realizer/workload/component.go +++ b/pkg/realizer/workload/component.go @@ -122,7 +122,7 @@ func (r *resourceRealizer) Do(ctx context.Context, resource *v1alpha1.SupplyChai } } - err = r.workloadRepo.EnsureObjectExistsOnCluster(ctx, stampedObject, true) + err = r.workloadRepo.EnsureMutableObjectExistsOnCluster(ctx, stampedObject) if err != nil { log.Error(err, "failed to ensure object exists on cluster", "object", stampedObject) return nil, nil, ApplyStampedObjectError{ diff --git a/pkg/realizer/workload/component_test.go b/pkg/realizer/workload/component_test.go index 0c1e57d8f..332df15ee 100644 --- a/pkg/realizer/workload/component_test.go +++ b/pkg/realizer/workload/component_test.go @@ -160,16 +160,15 @@ var _ = Describe("Resource", func() { } fakeSystemRepo.GetSupplyChainTemplateReturns(templateAPI, nil) - fakeWorkloadRepo.EnsureObjectExistsOnClusterReturns(nil) + fakeWorkloadRepo.EnsureMutableObjectExistsOnClusterReturns(nil) }) It("creates a stamped object using the workload repository and returns the outputs and stampedObjects", func() { returnedStampedObject, out, err := r.Do(ctx, &resource, supplyChainName, outputs) Expect(err).ToNot(HaveOccurred()) - _, stampedObject, allowUpdate := fakeWorkloadRepo.EnsureObjectExistsOnClusterArgsForCall(0) + _, stampedObject := fakeWorkloadRepo.EnsureMutableObjectExistsOnClusterArgsForCall(0) Expect(returnedStampedObject).To(Equal(stampedObject)) - Expect(allowUpdate).To(BeTrue()) metadata := stampedObject.Object["metadata"] metadataValues, ok := metadata.(map[string]interface{}) @@ -186,6 +185,7 @@ var _ = Describe("Resource", func() { "blockOwnerDeletion": true, }, })) + Expect(stampedObject.Object["data"]).To(Equal(map[string]interface{}{"player_current_lives": "some-url", "some_other_info": "some-revision"})) Expect(metadataValues["labels"]).To(Equal(map[string]interface{}{ "carto.run/cluster-supply-chain-name": "supply-chain-name", "carto.run/resource-name": "resource-1", @@ -194,7 +194,6 @@ var _ = Describe("Resource", func() { "carto.run/workload-namespace": "", "carto.run/template-kind": "ClusterImageTemplate", })) - Expect(stampedObject.Object["data"]).To(Equal(map[string]interface{}{"player_current_lives": "some-url", "some_other_info": "some-revision"})) Expect(out.Image).To(Equal("some-revision")) }) @@ -304,7 +303,7 @@ var _ = Describe("Resource", func() { } fakeSystemRepo.GetSupplyChainTemplateReturns(templateAPI, nil) - fakeWorkloadRepo.EnsureObjectExistsOnClusterReturns(nil) + fakeWorkloadRepo.EnsureMutableObjectExistsOnClusterReturns(nil) }) It("returns RetrieveOutputError", func() { @@ -315,7 +314,7 @@ var _ = Describe("Resource", func() { }) }) - When("unable to EnsureObjectExistsOnCluster the stamped object", func() { + When("unable to EnsureImmutableObjectExistsOnCluster the stamped object", func() { BeforeEach(func() { resource.Sources = []v1alpha1.ResourceReference{ { @@ -365,7 +364,7 @@ var _ = Describe("Resource", func() { } fakeSystemRepo.GetSupplyChainTemplateReturns(templateAPI, nil) - fakeWorkloadRepo.EnsureObjectExistsOnClusterReturns(errors.New("bad object")) + fakeWorkloadRepo.EnsureMutableObjectExistsOnClusterReturns(errors.New("bad object")) }) It("returns ApplyStampedObjectError", func() { _, _, err := r.Do(ctx, &resource, supplyChainName, outputs) diff --git a/pkg/repository/cache.go b/pkg/repository/cache.go index 8ee29d04c..f322d731c 100644 --- a/pkg/repository/cache.go +++ b/pkg/repository/cache.go @@ -29,7 +29,8 @@ type Logger interface { //counterfeiter:generate . RepoCache type RepoCache interface { Set(submitted, persisted *unstructured.Unstructured) - UnchangedSinceCached(local *unstructured.Unstructured, remote []*unstructured.Unstructured) *unstructured.Unstructured + UnchangedSinceCached(submitted *unstructured.Unstructured, existingObj *unstructured.Unstructured) *unstructured.Unstructured + UnchangedSinceCachedFromList(local *unstructured.Unstructured, remote []*unstructured.Unstructured) *unstructured.Unstructured } func NewCache(l Logger) RepoCache { @@ -52,51 +53,82 @@ func (c *cache) Set(submitted, persisted *unstructured.Unstructured) { c.persistedCache[key] = *persisted } -func (c *cache) UnchangedSinceCached(submitted *unstructured.Unstructured, existingList []*unstructured.Unstructured) *unstructured.Unstructured { +func (c *cache) UnchangedSinceCachedFromList(submitted *unstructured.Unstructured, existingList []*unstructured.Unstructured) *unstructured.Unstructured { key := getKey(submitted) c.logger.Info("checking for changes since cached", "key", key) - submittedCached, submittedFoundInCache := c.submittedCache[key] - submittedUnchanged := submittedFoundInCache && reflect.DeepEqual(submittedCached, *submitted) + if !c.isSubmittedCacheHit(submitted, key) { + return nil + } + + persistedCached := c.getPersistedCached(key) + + for _, existing := range existingList { + if c.isPersistedCacheHit(key, existing, persistedCached) { + return existing + } else { + continue + } + } + + c.logger.Info("miss: no matching existing object on apiserver", "key", key) + return nil +} + +func (c *cache) UnchangedSinceCached(submitted *unstructured.Unstructured, existingObj *unstructured.Unstructured) *unstructured.Unstructured { + key := getKey(submitted) + c.logger.Info("checking for changes since cached", "key", key) + if !c.isSubmittedCacheHit(submitted, key) { + return nil + } persistedCached := c.getPersistedCached(key) + if c.isPersistedCacheHit(key, existingObj, persistedCached) { + return existingObj + } else { + return nil + } +} + +func (c *cache) isSubmittedCacheHit(submitted *unstructured.Unstructured, key string) bool { + submittedCached, submittedFoundInCache := c.submittedCache[key] + submittedUnchanged := submittedFoundInCache && reflect.DeepEqual(submittedCached, *submitted) + if submittedUnchanged { c.logger.Info("no changes since last submission, checking existing objects on apiserver", "key", key) + return true } else { if submittedFoundInCache { c.logger.Info("miss: submitted object in cache is different from submitted object", "key", key) } else { c.logger.Info("miss: object not in cache", "key", key) } - return nil + return false } +} - for _, existing := range existingList { - c.logger.Info("considering object", "key", key, "existingName", existing.GetName()) - existingSpec, ok := existing.Object["spec"] - if !ok { - c.logger.Info("object on apiserver has no spec", "key", key) - continue - } - - persistedCachedSpec, ok := persistedCached.Object["spec"] - if !ok { - c.logger.Info("persisted object in cache has no spec", "key", key) - continue - } +func (c *cache) isPersistedCacheHit(key string, existingObj *unstructured.Unstructured, persistedCached *unstructured.Unstructured) bool { + c.logger.Info("considering object", "key", key, "existingName", existingObj.GetName()) + existingSpec, ok := existingObj.Object["spec"] + if !ok { + c.logger.Info("object on apiserver has no spec", "key", key) + return false + } - sameSame := reflect.DeepEqual(existingSpec, persistedCachedSpec) - if sameSame { - c.logger.Info("hit: persisted object in cache matches spec on apiserver", "key", key) - return existing - } else { - c.logger.Info("miss: persisted object in cache DOES NOT match spec on apiserver", "key", key) - continue - } + persistedCachedSpec, ok := persistedCached.Object["spec"] + if !ok { + c.logger.Info("persisted object in cache has no spec", "key", key) + return false } - c.logger.Info("miss: no matching existing object on apiserver", "key", key) - return nil + sameSame := reflect.DeepEqual(existingSpec, persistedCachedSpec) + if sameSame { + c.logger.Info("hit: persisted object in cache matches spec on apiserver", "key", key) + return true + } else { + c.logger.Info("miss: persisted object in cache DOES NOT match spec on apiserver", "key", key) + return false + } } func getKey(obj *unstructured.Unstructured) string { diff --git a/pkg/repository/cache_test.go b/pkg/repository/cache_test.go index be30a0fb7..91fec229c 100644 --- a/pkg/repository/cache_test.go +++ b/pkg/repository/cache_test.go @@ -49,7 +49,7 @@ var _ = Describe("Cache", func() { persisted.SetNamespace(objNamespace + "-ignored-submitted-one-is-used") }) - Describe("UnchangedSinceCached", func() { + Describe("UnchangedSinceCachedFromList", func() { Context("when the submitted object has a name", func() { var existingObjsOnAPIServer []*unstructured.Unstructured @@ -59,7 +59,7 @@ var _ = Describe("Cache", func() { Context("when the submitted object is not present in the cache", func() { It("is false", func() { - Expect(cache.UnchangedSinceCached(submitted, existingObjsOnAPIServer)).To(BeNil()) + Expect(cache.UnchangedSinceCachedFromList(submitted, existingObjsOnAPIServer)).To(BeNil()) }) }) @@ -71,7 +71,7 @@ var _ = Describe("Cache", func() { It("is false", func() { newSubmission := submitted.DeepCopy() newSubmission.SetLabels(map[string]string{"now-with": "funky-labels"}) - Expect(cache.UnchangedSinceCached(newSubmission, existingObjsOnAPIServer)).To(BeNil()) + Expect(cache.UnchangedSinceCachedFromList(newSubmission, existingObjsOnAPIServer)).To(BeNil()) }) }) @@ -82,7 +82,7 @@ var _ = Describe("Cache", func() { Context("when the existing object has no spec", func() { It("is false", func() { - Expect(cache.UnchangedSinceCached(submitted, existingObjsOnAPIServer)).To(BeNil()) + Expect(cache.UnchangedSinceCachedFromList(submitted, existingObjsOnAPIServer)).To(BeNil()) }) }) @@ -97,7 +97,7 @@ var _ = Describe("Cache", func() { }) It("is false", func() { - Expect(cache.UnchangedSinceCached(submitted, existingObjsOnAPIServer)).To(BeNil()) + Expect(cache.UnchangedSinceCachedFromList(submitted, existingObjsOnAPIServer)).To(BeNil()) }) }) @@ -109,7 +109,7 @@ var _ = Describe("Cache", func() { }) It("is true", func() { - Expect(cache.UnchangedSinceCached(submitted, existingObjsOnAPIServer)).ToNot(BeNil()) + Expect(cache.UnchangedSinceCachedFromList(submitted, existingObjsOnAPIServer)).ToNot(BeNil()) }) }) @@ -120,7 +120,7 @@ var _ = Describe("Cache", func() { }) It("is false", func() { - Expect(cache.UnchangedSinceCached(submitted, existingObjsOnAPIServer)).To(BeNil()) + Expect(cache.UnchangedSinceCachedFromList(submitted, existingObjsOnAPIServer)).To(BeNil()) }) }) }) @@ -144,6 +144,109 @@ var _ = Describe("Cache", func() { existingObjsOnAPIServer = append(existingObjsOnAPIServer, persisted.DeepCopy()) }) + It("the cache matches against the generateName instead", func() { + Expect(cache.UnchangedSinceCachedFromList(submitted, existingObjsOnAPIServer)).ToNot(BeNil()) + submitted.SetGenerateName("another-generate-name-") + Expect(cache.UnchangedSinceCachedFromList(submitted, existingObjsOnAPIServer)).To(BeNil()) + }) + }) + }) + + Describe("UnchangedSinceCached", func() { + Context("when the submitted object has a name", func() { + var existingObjOnAPIServer *unstructured.Unstructured + + BeforeEach(func() { + existingObjOnAPIServer = persisted.DeepCopy() + }) + + Context("when the submitted object is not present in the cache", func() { + It("is false", func() { + Expect(cache.UnchangedSinceCached(submitted, existingObjOnAPIServer)).To(BeNil()) + }) + }) + + Context("when the submitted object differs from the cached submitted object", func() { + BeforeEach(func() { + cache.Set(submitted, persisted) + }) + + It("is false", func() { + newSubmission := submitted.DeepCopy() + newSubmission.SetLabels(map[string]string{"now-with": "funky-labels"}) + Expect(cache.UnchangedSinceCached(newSubmission, existingObjOnAPIServer)).To(BeNil()) + }) + }) + + Context("when the submitted object is the same as the cached submitted object", func() { + BeforeEach(func() { + cache.Set(submitted, persisted) + }) + + Context("when the existing object has no spec", func() { + It("is false", func() { + Expect(cache.UnchangedSinceCached(submitted, existingObjOnAPIServer)).To(BeNil()) + }) + }) + + Context("when the existing object has a spec", func() { + BeforeEach(func() { + existingObjOnAPIServer.UnstructuredContent()["spec"] = map[string]interface{}{"oh-look": "its-a-spec"} + }) + + Context("when the persisted object has no spec", func() { + BeforeEach(func() { + cache.Set(submitted, persisted) + }) + + It("is false", func() { + Expect(cache.UnchangedSinceCached(submitted, existingObjOnAPIServer)).To(BeNil()) + }) + }) + + Context("when the persisted object has a spec", func() { + Context("when the existing object spec is the same as the cached submitted object spec", func() { + BeforeEach(func() { + persisted.UnstructuredContent()["spec"] = existingObjOnAPIServer.UnstructuredContent()["spec"] + cache.Set(submitted, persisted) + }) + + It("is true", func() { + Expect(cache.UnchangedSinceCached(submitted, existingObjOnAPIServer)).ToNot(BeNil()) + }) + }) + + Context("when the existing object spec differs from the cached submitted object spec", func() { + BeforeEach(func() { + persisted.UnstructuredContent()["spec"] = map[string]interface{}{"oh-wait": "this-spec-is-different"} + cache.Set(submitted, persisted) + }) + + It("is false", func() { + Expect(cache.UnchangedSinceCached(submitted, existingObjOnAPIServer)).To(BeNil()) + }) + }) + }) + }) + }) + }) + + Context("when the submitted object has no name", func() { + var existingObjsOnAPIServer *unstructured.Unstructured + + BeforeEach(func() { + submitted.SetName("") + submitted.SetGenerateName("this-is-generate-name-") + submitted.UnstructuredContent()["spec"] = map[string]interface{}{"ooo": "a-spec"} + + persisted.SetName("this-is-generate-name-abcdef") + persisted.SetGenerateName("") + persisted.UnstructuredContent()["spec"] = submitted.UnstructuredContent()["spec"] + + cache.Set(submitted, persisted) + existingObjsOnAPIServer = persisted.DeepCopy() + }) + It("the cache matches against the generateName instead", func() { Expect(cache.UnchangedSinceCached(submitted, existingObjsOnAPIServer)).ToNot(BeNil()) submitted.SetGenerateName("another-generate-name-") diff --git a/pkg/repository/repository.go b/pkg/repository/repository.go index 34987a9b4..0eee04713 100644 --- a/pkg/repository/repository.go +++ b/pkg/repository/repository.go @@ -21,9 +21,10 @@ import ( "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" kerrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/vmware-tanzu/cartographer/pkg/apis/v1alpha1" @@ -36,7 +37,8 @@ import ( //counterfeiter:generate . Repository type Repository interface { - EnsureObjectExistsOnCluster(ctx context.Context, obj *unstructured.Unstructured, allowUpdate bool) error + EnsureImmutableObjectExistsOnCluster(ctx context.Context, obj *unstructured.Unstructured, labels map[string]string) error + EnsureMutableObjectExistsOnCluster(ctx context.Context, obj *unstructured.Unstructured) error GetSupplyChainTemplate(ctx context.Context, ref v1alpha1.SupplyChainTemplateReference) (client.Object, error) GetDeliveryTemplate(ctx context.Context, ref v1alpha1.DeliveryTemplateReference) (client.Object, error) GetRunTemplate(ctx context.Context, ref v1alpha1.TemplateReference) (*v1alpha1.ClusterRunTemplate, error) @@ -47,7 +49,8 @@ type Repository interface { GetSupplyChain(ctx context.Context, name string) (*v1alpha1.ClusterSupplyChain, error) StatusUpdate(ctx context.Context, object client.Object) error GetRunnable(ctx context.Context, name string, namespace string) (*v1alpha1.Runnable, error) - ListUnstructured(ctx context.Context, obj *unstructured.Unstructured) ([]*unstructured.Unstructured, error) + GetUnstructured(ctx context.Context, obj *unstructured.Unstructured) (*unstructured.Unstructured, error) + ListUnstructured(ctx context.Context, gvk schema.GroupVersionKind, namespace string, labels map[string]string) ([]*unstructured.Unstructured, error) GetDelivery(ctx context.Context, name string) (*v1alpha1.ClusterDelivery, error) GetScheme() *runtime.Scheme GetServiceAccountSecret(ctx context.Context, serviceAccountName, ns string) (*corev1.Secret, error) @@ -125,11 +128,37 @@ func (r *repository) GetDelivery(ctx context.Context, name string) (*v1alpha1.Cl return delivery, nil } -func (r *repository) EnsureObjectExistsOnCluster(ctx context.Context, obj *unstructured.Unstructured, allowUpdate bool) error { +func (r *repository) EnsureMutableObjectExistsOnCluster(ctx context.Context, obj *unstructured.Unstructured) error { + log := logr.FromContextOrDiscard(ctx) + log.V(logger.DEBUG).Info("EnsureMutableObjectExistsOnCluster") + + existingObj, err := r.GetUnstructured(ctx, obj) + log.V(logger.DEBUG).Info("considering object from api server", + "considered", obj) + + if err != nil { + return err + } + + if existingObj != nil { + cacheHit := r.rc.UnchangedSinceCached(obj, existingObj) + if cacheHit != nil { + *obj = *cacheHit + return nil + } + log.Info("patching object", "object", obj) + return r.patchUnstructured(ctx, existingObj, obj) + } else { + log.Info("creating object", "object", obj) + return r.createUnstructured(ctx, obj) + } +} + +func (r *repository) EnsureImmutableObjectExistsOnCluster(ctx context.Context, obj *unstructured.Unstructured, labels map[string]string) error { log := logr.FromContextOrDiscard(ctx) - log.V(logger.DEBUG).Info("EnsureObjectExistsOnCluster") + log.V(logger.DEBUG).Info("EnsureImmutableObjectExistsOnCluster") - unstructuredList, err := r.ListUnstructured(ctx, obj) + unstructuredList, err := r.ListUnstructured(ctx, obj.GroupVersionKind(), obj.GetNamespace(), labels) for _, considered := range unstructuredList { log.V(logger.DEBUG).Info("considering objects from api server", @@ -140,48 +169,59 @@ func (r *repository) EnsureObjectExistsOnCluster(ctx context.Context, obj *unstr return err } - cacheHit := r.rc.UnchangedSinceCached(obj, unstructuredList) + cacheHit := r.rc.UnchangedSinceCachedFromList(obj, unstructuredList) if cacheHit != nil { *obj = *cacheHit return nil } - var outdatedObject *unstructured.Unstructured - if allowUpdate { - outdatedObject = getOutdatedUnstructuredByName(obj, unstructuredList) - } + log.Info("creating object", "object", obj) + return r.createUnstructured(ctx, obj) +} - if outdatedObject != nil { - log.Info("patching object", "object", obj) - return r.patchUnstructured(ctx, outdatedObject, obj) - } else { - log.Info("creating object", "object", obj) - return r.createUnstructured(ctx, obj) +func (r *repository) GetUnstructured(ctx context.Context, obj *unstructured.Unstructured) (*unstructured.Unstructured, error) { + log := logr.FromContextOrDiscard(ctx) + log.V(logger.DEBUG).Info("GetUnstructured") + + objKey := client.ObjectKey{ + Name: obj.GetName(), + Namespace: obj.GetNamespace(), } -} -func getOutdatedUnstructuredByName(target *unstructured.Unstructured, candidates []*unstructured.Unstructured) *unstructured.Unstructured { - for _, candidate := range candidates { - if candidate.GetName() == target.GetName() && candidate.GetNamespace() == target.GetNamespace() { - return candidate + log.V(logger.DEBUG).Info("get unstructured with name and namespace", + "name", obj.GetName(), "namespace", obj.GetNamespace()) + + returnObj := &unstructured.Unstructured{} + returnObj.SetGroupVersionKind(obj.GroupVersionKind()) + err := r.cl.Get(ctx, objKey, returnObj) + if err != nil { + if kerrors.IsNotFound(err) { + return nil, nil } + namespacedName := types.NamespacedName{ + Namespace: obj.GetNamespace(), + Name: obj.GetName(), + } + log.Error(err, "failed to get unstructured from api server", "object", namespacedName) + return nil, fmt.Errorf("failed to get unstructured [%s] from api server: %w", namespacedName, err) } - return nil + + return returnObj, nil } -func (r *repository) ListUnstructured(ctx context.Context, obj *unstructured.Unstructured) ([]*unstructured.Unstructured, error) { +func (r *repository) ListUnstructured(ctx context.Context, gvk schema.GroupVersionKind, namespace string, labels map[string]string) ([]*unstructured.Unstructured, error) { log := logr.FromContextOrDiscard(ctx) log.V(logger.DEBUG).Info("ListUnstructured") unstructuredList := &unstructured.UnstructuredList{} - unstructuredList.SetGroupVersionKind(obj.GroupVersionKind()) + unstructuredList.SetGroupVersionKind(gvk) opts := []client.ListOption{ - client.InNamespace(obj.GetNamespace()), - client.MatchingLabels(obj.GetLabels()), + client.InNamespace(namespace), + client.MatchingLabels(labels), } log.V(logger.DEBUG).Info("list unstructured with namespace and labels", - "namespace", obj.GetNamespace(), "labels", obj.GetLabels()) + "namespace", namespace, "labels", labels) err := r.cl.List(ctx, unstructuredList, opts...) if err != nil { log.Error(err, "unable to list from api server") @@ -193,7 +233,7 @@ func (r *repository) ListUnstructured(ctx context.Context, obj *unstructured.Uns //FIXME: why are we taking a deep copy? for i, item := range unstructuredList.Items { log.V(logger.DEBUG).Info("unstructured that matched", - "namespace", obj.GetNamespace(), "labels", obj.GetLabels(), "unstructured", item) + "namespace", namespace, "labels", labels, "unstructured", item) pointersToUnstructureds[i] = item.DeepCopy() } return pointersToUnstructureds, nil diff --git a/pkg/repository/repository_test.go b/pkg/repository/repository_test.go index 903288ade..9e791c3c3 100644 --- a/pkg/repository/repository_test.go +++ b/pkg/repository/repository_test.go @@ -30,6 +30,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer/yaml" + "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" @@ -60,8 +61,10 @@ var _ = Describe("repository", func() { repo = repository.NewRepository(cl, cache) }) - Context("EnsureObjectExistsOnCluster", func() { - var stampedObj *unstructured.Unstructured + Context("EnsureMutableObjectExistsOnCluster", func() { + var ( + stampedObj *unstructured.Unstructured + ) BeforeEach(func() { stampedObj = &unstructured.Unstructured{} @@ -86,51 +89,44 @@ spec: }) It("attempts to get the object from the apiServer", func() { - Expect(repo.EnsureObjectExistsOnCluster(ctx, stampedObj, true)).To(Succeed()) + Expect(repo.EnsureMutableObjectExistsOnCluster(ctx, stampedObj)).To(Succeed()) - Expect(cl.ListCallCount()).To(Equal(1)) - - listOptions := []client.ListOption{ - client.InNamespace(stampedObj.GetNamespace()), - client.MatchingLabels(stampedObj.GetLabels()), - } + Expect(cl.GetCallCount()).To(Equal(1)) - _, objectList, options := cl.ListArgsForCall(0) - unstructuredList, ok := objectList.(*unstructured.UnstructuredList) - Expect(ok).To(BeTrue()) - Expect(len(unstructuredList.Items)).To(Equal(0)) - Expect(options).To(Equal(listOptions)) - Expect(unstructuredList.GetObjectKind().GroupVersionKind()).To(Equal(stampedObj.GroupVersionKind())) + _, namespacedName, obj := cl.GetArgsForCall(0) + Expect(namespacedName).To(Equal(types.NamespacedName{Namespace: "default", Name: "hello"})) + Expect(obj.GetObjectKind().GroupVersionKind()).To(Equal(stampedObj.GroupVersionKind())) }) Context("when the apiServer errors when trying to get the object", func() { BeforeEach(func() { - cl.ListReturns(errors.New("some-error")) + cl.GetReturns(errors.New("some-error")) }) It("returns a helpful error", func() { - err := repo.EnsureObjectExistsOnCluster(ctx, stampedObj, true) - Expect(err).To(MatchError(ContainSubstring("unable to list from api server: some-error"))) + err := repo.EnsureMutableObjectExistsOnCluster(ctx, stampedObj) + Expect(err).To(MatchError(ContainSubstring("failed to get unstructured [default/hello] from api server: some-error"))) }) It("does not create or patch any objects", func() { - _ = repo.EnsureObjectExistsOnCluster(ctx, stampedObj, true) + _ = repo.EnsureMutableObjectExistsOnCluster(ctx, stampedObj) Expect(cl.CreateCallCount()).To(Equal(0)) Expect(cl.PatchCallCount()).To(Equal(0)) }) It("does not write to the submitted or persisted cache", func() { - _ = repo.EnsureObjectExistsOnCluster(ctx, stampedObj, true) + _ = repo.EnsureMutableObjectExistsOnCluster(ctx, stampedObj) Expect(cache.SetCallCount()).To(Equal(0)) }) }) Context("and the apiServer attempts to get the object and it doesn't exist", func() { BeforeEach(func() { - // default behavior is empty list - no need to stub + cl.GetReturns(kerrors.NewNotFound(schema.GroupResource{}, "")) }) + It("attempts to create the object", func() { - Expect(repo.EnsureObjectExistsOnCluster(ctx, stampedObj, true)).To(Succeed()) + Expect(repo.EnsureMutableObjectExistsOnCluster(ctx, stampedObj)).To(Succeed()) Expect(cl.CreateCallCount()).To(Equal(1)) _, createCallObj, _ := cl.CreateArgsForCall(0) @@ -143,12 +139,12 @@ spec: }) It("returns a helpful error", func() { - err := repo.EnsureObjectExistsOnCluster(ctx, stampedObj, true) + err := repo.EnsureMutableObjectExistsOnCluster(ctx, stampedObj) Expect(err).To(MatchError(ContainSubstring("create: some-error"))) }) It("does not write to the submitted or persisted cache", func() { - _ = repo.EnsureObjectExistsOnCluster(ctx, stampedObj, true) + _ = repo.EnsureMutableObjectExistsOnCluster(ctx, stampedObj) Expect(cache.SetCallCount()).To(Equal(0)) }) }) @@ -168,13 +164,13 @@ spec: }) It("does not return an error", func() { - Expect(repo.EnsureObjectExistsOnCluster(ctx, stampedObj, true)).To(Succeed()) + Expect(repo.EnsureMutableObjectExistsOnCluster(ctx, stampedObj)).To(Succeed()) }) It("caches the submitted and persisted objects, as the persisted one may be modified by mutating webhooks", func() { originalStampedObj := stampedObj.DeepCopy() - Expect(repo.EnsureObjectExistsOnCluster(ctx, stampedObj, true)).To(Succeed()) + Expect(repo.EnsureMutableObjectExistsOnCluster(ctx, stampedObj)).To(Succeed()) Expect(cache.SetCallCount()).To(Equal(1)) submitted, persisted := cache.SetArgsForCall(0) Expect(*submitted).To(Equal(*originalStampedObj)) @@ -183,10 +179,9 @@ spec: }) }) - Context("and apiServer succeeds in getting the list of object(s)", func() { + Context("and apiServer succeeds in getting the object", func() { var ( - existingObj *unstructured.Unstructured - existingObjList unstructured.UnstructuredList + existingObj *unstructured.Unstructured ) BeforeEach(func() { @@ -195,25 +190,22 @@ spec: existingObj.SetNamespace("default") existingObj.SetGeneration(5) - existingObjList = unstructured.UnstructuredList{ - Items: []unstructured.Unstructured{*existingObj}, - } - cl.ListStub = func(ctx context.Context, list client.ObjectList, option ...client.ListOption) error { - listVal := reflect.ValueOf(list) - existingVal := reflect.ValueOf(existingObjList) + cl.GetStub = func(ctx context.Context, key types.NamespacedName, obj client.Object) error { + objVal := reflect.ValueOf(obj) + existingVal := reflect.ValueOf(existingObj) - reflect.Indirect(listVal).Set(reflect.Indirect(existingVal)) + reflect.Indirect(objVal).Set(reflect.Indirect(existingVal)) return nil } }) It("the cache is consulted to see if there was a change since the last time the cache was updated", func() { - Expect(repo.EnsureObjectExistsOnCluster(ctx, stampedObj, true)).To(Succeed()) + Expect(repo.EnsureMutableObjectExistsOnCluster(ctx, stampedObj)).To(Succeed()) Expect(cache.UnchangedSinceCachedCallCount()).To(Equal(1)) submitted, persisted := cache.UnchangedSinceCachedArgsForCall(0) Expect(*submitted).To(Equal(*stampedObj)) - Expect(persisted[0]).To(Equal(existingObj)) + Expect(persisted).To(Equal(existingObj)) }) Context("and the cache determines there has been no change since the last update", func() { @@ -222,20 +214,20 @@ spec: }) It("does not create or patch any objects", func() { - Expect(repo.EnsureObjectExistsOnCluster(ctx, stampedObj, true)).To(Succeed()) + Expect(repo.EnsureMutableObjectExistsOnCluster(ctx, stampedObj)).To(Succeed()) Expect(cl.CreateCallCount()).To(Equal(0)) Expect(cl.PatchCallCount()).To(Equal(0)) }) It("does not write to the submitted or persisted cache", func() { - Expect(repo.EnsureObjectExistsOnCluster(ctx, stampedObj, true)).To(Succeed()) + Expect(repo.EnsureMutableObjectExistsOnCluster(ctx, stampedObj)).To(Succeed()) Expect(cache.SetCallCount()).To(Equal(0)) }) It("populates the object passed into the function with the object in apiServer", func() { originalStampedObj := stampedObj.DeepCopy() - _ = repo.EnsureObjectExistsOnCluster(ctx, stampedObj, true) + _ = repo.EnsureMutableObjectExistsOnCluster(ctx, stampedObj) Expect(stampedObj).To(Equal(existingObj)) Expect(stampedObj).NotTo(Equal(originalStampedObj)) @@ -250,7 +242,7 @@ spec: Context("and allowUpdate is true", func() { Context("list has exactly one object", func() { It("patches the object", func() { - Expect(repo.EnsureObjectExistsOnCluster(ctx, stampedObj, true)).To(Succeed()) + Expect(repo.EnsureMutableObjectExistsOnCluster(ctx, stampedObj)).To(Succeed()) Expect(cl.PatchCallCount()).To(Equal(1)) }) @@ -270,13 +262,13 @@ spec: }) It("does not return an error", func() { - Expect(repo.EnsureObjectExistsOnCluster(ctx, stampedObj, true)).To(Succeed()) + Expect(repo.EnsureMutableObjectExistsOnCluster(ctx, stampedObj)).To(Succeed()) }) It("caches the submitted and persisted objects, as the persisted one may be modified by mutating webhooks", func() { originalStampedObj := stampedObj.DeepCopy() - Expect(repo.EnsureObjectExistsOnCluster(ctx, stampedObj, true)).To(Succeed()) + Expect(repo.EnsureMutableObjectExistsOnCluster(ctx, stampedObj)).To(Succeed()) Expect(cache.SetCallCount()).To(Equal(1)) submitted, persisted := cache.SetArgsForCall(0) Expect(*submitted).To(Equal(*originalStampedObj)) @@ -289,107 +281,194 @@ spec: cl.PatchReturns(errors.New("some-error")) }) It("returns a helpful error", func() { - err := repo.EnsureObjectExistsOnCluster(ctx, stampedObj, true) + err := repo.EnsureMutableObjectExistsOnCluster(ctx, stampedObj) Expect(err).To(MatchError(ContainSubstring("patch: some-error"))) }) It("does not write to the submitted or persisted cache", func() { - _ = repo.EnsureObjectExistsOnCluster(ctx, stampedObj, true) + _ = repo.EnsureMutableObjectExistsOnCluster(ctx, stampedObj) Expect(cache.SetCallCount()).To(Equal(0)) }) }) }) + }) + }) + }) + }) - Context("list has more than one object", func() { - Context("and the list contains the correct object", func() { - BeforeEach(func() { - rogueObjectWithDuplicateLabels := existingObj.DeepCopy() - Expect(utils.AlterFieldOfNestedStringMaps(rogueObjectWithDuplicateLabels.Object, "metadata.name", "goodbye")).To(Succeed()) - existingObjList = unstructured.UnstructuredList{ - Items: []unstructured.Unstructured{*existingObj, *rogueObjectWithDuplicateLabels}, - } - }) + Context("EnsureImmutableObjectExistsOnCluster", func() { + var ( + stampedObj *unstructured.Unstructured + labels map[string]string + ) - It("it patches", func() { - Expect(repo.EnsureObjectExistsOnCluster(ctx, stampedObj, true)).To(Succeed()) - Expect(cl.PatchCallCount()).To(Equal(1)) - }) - }) + BeforeEach(func() { + labels = map[string]string{"foo": "bar"} + stampedObj = &unstructured.Unstructured{} + stampedObjManifest := ` +apiVersion: batch/v1 +kind: Job +metadata: + name: hello + namespace: default + labels: + foo: bar +spec: + template: + spec: + containers: + - name: hello + image: busybox + command: ['sh', '-c', 'echo "Hello, Kubernetes!" && sleep 3600'] + restartPolicy: OnFailure +` + dec := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme) + _, _, err := dec.Decode([]byte(stampedObjManifest), nil, stampedObj) + Expect(err).NotTo(HaveOccurred()) + }) - Context("and the list does not contain the correct object", func() { - BeforeEach(func() { - rogueObjectWithDuplicateLabels := existingObj.DeepCopy() - Expect(utils.AlterFieldOfNestedStringMaps(rogueObjectWithDuplicateLabels.Object, "metadata.name", "goodbye")).To(Succeed()) - secondRogueObjectWithDuplicateLabels := existingObj.DeepCopy() - Expect(utils.AlterFieldOfNestedStringMaps(secondRogueObjectWithDuplicateLabels.Object, "metadata.name", "farewell")).To(Succeed()) - existingObjList = unstructured.UnstructuredList{ - Items: []unstructured.Unstructured{*rogueObjectWithDuplicateLabels, *secondRogueObjectWithDuplicateLabels}, - } - }) - It("it creates", func() { - Expect(repo.EnsureObjectExistsOnCluster(ctx, stampedObj, true)).To(Succeed()) - Expect(cl.CreateCallCount()).To(Equal(1)) - }) - }) - }) + Context("and apiServer succeeds in getting the list of objects", func() { + var ( + existingObj *unstructured.Unstructured + existingObjList unstructured.UnstructuredList + ) + + BeforeEach(func() { + existingObj = &unstructured.Unstructured{} + existingObj.SetName("hello") + existingObj.SetNamespace("default") + existingObj.SetGeneration(5) + + existingObjList = unstructured.UnstructuredList{ + Items: []unstructured.Unstructured{*existingObj}, + } + + cl.ListStub = func(ctx context.Context, list client.ObjectList, options ...client.ListOption) error { + listVal := reflect.ValueOf(list) + existingVal := reflect.ValueOf(existingObjList) + + reflect.Indirect(listVal).Set(reflect.Indirect(existingVal)) + return nil + } + }) + + It("the cache is consulted to see if there was a change since the last time the cache was updated", func() { + Expect(repo.EnsureImmutableObjectExistsOnCluster(ctx, stampedObj, labels)).To(Succeed()) + Expect(cache.UnchangedSinceCachedFromListCallCount()).To(Equal(1)) + + submitted, persisted := cache.UnchangedSinceCachedFromListArgsForCall(0) + Expect(*submitted).To(Equal(*stampedObj)) + Expect(persisted[0]).To(Equal(existingObj)) + }) + + Context("and the cache determines there has been no change since the last update", func() { + BeforeEach(func() { + cache.UnchangedSinceCachedFromListReturns(existingObj) }) - Context("and allowUpate is false", func() { - It("creates a new object", func() { - Expect(repo.EnsureObjectExistsOnCluster(ctx, stampedObj, false)).To(Succeed()) - Expect(cl.PatchCallCount()).To(Equal(0)) - Expect(cl.CreateCallCount()).To(Equal(1)) - }) + It("does not create any objects", func() { + Expect(repo.EnsureImmutableObjectExistsOnCluster(ctx, stampedObj, labels)).To(Succeed()) + Expect(cl.CreateCallCount()).To(Equal(0)) + }) - Context("and the create succeeds", func() { - var returnedCreatedObj *unstructured.Unstructured + It("does not write to the submitted or persisted cache", func() { + Expect(repo.EnsureImmutableObjectExistsOnCluster(ctx, stampedObj, labels)).To(Succeed()) + Expect(cache.SetCallCount()).To(Equal(0)) + }) - BeforeEach(func() { - returnedCreatedObj = stampedObj.DeepCopy() - Expect(utils.AlterFieldOfNestedStringMaps(returnedCreatedObj.Object, "spec.template.spec.restartPolicy", "Never")).To(Succeed()) - cl.CreateStub = func(ctx context.Context, object client.Object, option ...client.CreateOption) error { - objVal := reflect.ValueOf(object) - returnVal := reflect.ValueOf(returnedCreatedObj) + It("populates the object passed into the function with the object in apiServer", func() { + originalStampedObj := stampedObj.DeepCopy() - reflect.Indirect(objVal).Set(reflect.Indirect(returnVal)) - return nil - } - }) + _ = repo.EnsureImmutableObjectExistsOnCluster(ctx, stampedObj, labels) - It("does not return an error", func() { - Expect(repo.EnsureObjectExistsOnCluster(ctx, stampedObj, false)).To(Succeed()) - }) + Expect(stampedObj).To(Equal(existingObj)) + Expect(stampedObj).NotTo(Equal(originalStampedObj)) + }) + }) + + Context("and the cache determines there has been a change since the last update", func() { + BeforeEach(func() { + cache.UnchangedSinceCachedReturns(nil) + }) - It("caches the submitted and persisted objects, as the persisted one may be modified by mutating webhooks", func() { - originalStampedObj := stampedObj.DeepCopy() + It("creates a new object", func() { + Expect(repo.EnsureImmutableObjectExistsOnCluster(ctx, stampedObj, labels)).To(Succeed()) + Expect(cl.CreateCallCount()).To(Equal(1)) + }) - Expect(repo.EnsureObjectExistsOnCluster(ctx, stampedObj, false)).To(Succeed()) - Expect(cache.SetCallCount()).To(Equal(1)) - submitted, persisted := cache.SetArgsForCall(0) - Expect(*submitted).To(Equal(*originalStampedObj)) - Expect(*persisted).To(Equal(*returnedCreatedObj)) - }) + Context("and the create succeeds", func() { + var returnedCreatedObj *unstructured.Unstructured + + BeforeEach(func() { + returnedCreatedObj = stampedObj.DeepCopy() + Expect(utils.AlterFieldOfNestedStringMaps(returnedCreatedObj.Object, "spec.template.spec.restartPolicy", "Never")).To(Succeed()) + cl.CreateStub = func(ctx context.Context, object client.Object, option ...client.CreateOption) error { + objVal := reflect.ValueOf(object) + returnVal := reflect.ValueOf(returnedCreatedObj) + + reflect.Indirect(objVal).Set(reflect.Indirect(returnVal)) + return nil + } }) - Context("and the create fails", func() { - BeforeEach(func() { - cl.CreateReturns(errors.New("some-error")) - }) - It("returns a helpful error", func() { - err := repo.EnsureObjectExistsOnCluster(ctx, stampedObj, false) - Expect(err).To(MatchError(ContainSubstring("create: some-error"))) - }) + It("does not return an error", func() { + Expect(repo.EnsureImmutableObjectExistsOnCluster(ctx, stampedObj, labels)).To(Succeed()) + }) - It("does not write to the submitted or persisted cache", func() { - _ = repo.EnsureObjectExistsOnCluster(ctx, stampedObj, false) - Expect(cache.SetCallCount()).To(Equal(0)) - }) + It("caches the submitted and persisted objects, as the persisted one may be modified by mutating webhooks", func() { + originalStampedObj := stampedObj.DeepCopy() + + Expect(repo.EnsureImmutableObjectExistsOnCluster(ctx, stampedObj, labels)).To(Succeed()) + Expect(cache.SetCallCount()).To(Equal(1)) + submitted, persisted := cache.SetArgsForCall(0) + Expect(*submitted).To(Equal(*originalStampedObj)) + Expect(*persisted).To(Equal(*returnedCreatedObj)) + }) + }) + + Context("and the create fails", func() { + BeforeEach(func() { + cl.CreateReturns(errors.New("some-error")) + }) + It("returns a helpful error", func() { + err := repo.EnsureImmutableObjectExistsOnCluster(ctx, stampedObj, labels) + Expect(err).To(MatchError(ContainSubstring("create: some-error"))) + }) + + It("does not write to the submitted or persisted cache", func() { + _ = repo.EnsureImmutableObjectExistsOnCluster(ctx, stampedObj, labels) + Expect(cache.SetCallCount()).To(Equal(0)) }) }) }) }) }) + Context("ListUnstructured", func() { + It("attempts to list objects from the apiServer", func() { + namespace := "some-namespace" + labels := map[string]string{"some-key": "some-value"} + gvk := schema.GroupVersionKind{} + + _, err := repo.ListUnstructured(ctx, gvk, namespace, labels) + Expect(err).NotTo(HaveOccurred()) + + Expect(cl.ListCallCount()).To(Equal(1)) + + expectedListOptions := []client.ListOption{ + client.InNamespace(namespace), + client.MatchingLabels(labels), + } + + _, objectList, options := cl.ListArgsForCall(0) + unstructuredList, ok := objectList.(*unstructured.UnstructuredList) + Expect(ok).To(BeTrue()) + Expect(len(unstructuredList.Items)).To(Equal(0)) + Expect(options).To(Equal(expectedListOptions)) + Expect(unstructuredList.GetObjectKind().GroupVersionKind()).To(Equal(gvk)) + }) + }) + Context("GetSupplyChainsForWorkload", func() { BeforeEach(func() { cl.ListReturns(errors.New("some list error")) @@ -414,6 +493,85 @@ spec: }) }) + Context("GetUnstructured", func() { + Context("get returns an error", func() { + Context("the error is of type IsNotFound", func() { + BeforeEach(func() { + cl.GetReturns(kerrors.NewNotFound(schema.GroupResource{}, "")) + }) + + It("returns a nil object and no error", func() { + obj := &unstructured.Unstructured{} + + obj.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "my-group", + Version: "my-version", + Kind: "my-kind", + }) + obj.SetNamespace("my-ns") + obj.SetName("my-name") + returnedObj, err := repo.GetUnstructured(ctx, obj) + Expect(err).NotTo(HaveOccurred()) + Expect(returnedObj).To(BeNil()) + }) + }) + + Context("the error is not of type IsNotFound", func() { + BeforeEach(func() { + cl.GetReturns(errors.New("some get error")) + }) + + It("errors with a helfpul error message", func() { + obj := &unstructured.Unstructured{} + + obj.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "my-group", + Version: "my-version", + Kind: "my-kind", + }) + obj.SetNamespace("my-ns") + obj.SetName("my-name") + _, err := repo.GetUnstructured(ctx, obj) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("failed to get unstructured [my-ns/my-name] from api server: some get error")) + }) + }) + }) + + Context("get does not return an error", func() { + var existingObj *unstructured.Unstructured + BeforeEach(func() { + existingObj = &unstructured.Unstructured{} + existingObj.SetName("hello") + existingObj.SetNamespace("default") + existingObj.SetGeneration(5) + + cl.GetStub = func(ctx context.Context, key types.NamespacedName, obj client.Object) error { + objVal := reflect.ValueOf(obj) + existingVal := reflect.ValueOf(existingObj) + + reflect.Indirect(objVal).Set(reflect.Indirect(existingVal)) + return nil + } + }) + + It("successfully gets unstructured from api server", func() { + obj := &unstructured.Unstructured{} + + obj.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "my-group", + Version: "my-version", + Kind: "my-kind", + }) + obj.SetNamespace("my-ns") + obj.SetName("my-name") + returnedObj, err := repo.GetUnstructured(ctx, obj) + Expect(err).NotTo(HaveOccurred()) + Expect(returnedObj).To(Equal(existingObj)) + }) + }) + }) + Context("GetSupplyChainTemplate", func() { Context("when the template reference kind is not in our gvk", func() { It("returns a helpful error", func() { diff --git a/pkg/repository/repositoryfakes/fake_repo_cache.go b/pkg/repository/repositoryfakes/fake_repo_cache.go index f4eebc629..ff52cb55f 100644 --- a/pkg/repository/repositoryfakes/fake_repo_cache.go +++ b/pkg/repository/repositoryfakes/fake_repo_cache.go @@ -15,11 +15,11 @@ type FakeRepoCache struct { arg1 *unstructured.Unstructured arg2 *unstructured.Unstructured } - UnchangedSinceCachedStub func(*unstructured.Unstructured, []*unstructured.Unstructured) *unstructured.Unstructured + UnchangedSinceCachedStub func(*unstructured.Unstructured, *unstructured.Unstructured) *unstructured.Unstructured unchangedSinceCachedMutex sync.RWMutex unchangedSinceCachedArgsForCall []struct { arg1 *unstructured.Unstructured - arg2 []*unstructured.Unstructured + arg2 *unstructured.Unstructured } unchangedSinceCachedReturns struct { result1 *unstructured.Unstructured @@ -27,6 +27,18 @@ type FakeRepoCache struct { unchangedSinceCachedReturnsOnCall map[int]struct { result1 *unstructured.Unstructured } + UnchangedSinceCachedFromListStub func(*unstructured.Unstructured, []*unstructured.Unstructured) *unstructured.Unstructured + unchangedSinceCachedFromListMutex sync.RWMutex + unchangedSinceCachedFromListArgsForCall []struct { + arg1 *unstructured.Unstructured + arg2 []*unstructured.Unstructured + } + unchangedSinceCachedFromListReturns struct { + result1 *unstructured.Unstructured + } + unchangedSinceCachedFromListReturnsOnCall map[int]struct { + result1 *unstructured.Unstructured + } invocations map[string][][]interface{} invocationsMutex sync.RWMutex } @@ -64,21 +76,16 @@ func (fake *FakeRepoCache) SetArgsForCall(i int) (*unstructured.Unstructured, *u return argsForCall.arg1, argsForCall.arg2 } -func (fake *FakeRepoCache) UnchangedSinceCached(arg1 *unstructured.Unstructured, arg2 []*unstructured.Unstructured) *unstructured.Unstructured { - var arg2Copy []*unstructured.Unstructured - if arg2 != nil { - arg2Copy = make([]*unstructured.Unstructured, len(arg2)) - copy(arg2Copy, arg2) - } +func (fake *FakeRepoCache) UnchangedSinceCached(arg1 *unstructured.Unstructured, arg2 *unstructured.Unstructured) *unstructured.Unstructured { fake.unchangedSinceCachedMutex.Lock() ret, specificReturn := fake.unchangedSinceCachedReturnsOnCall[len(fake.unchangedSinceCachedArgsForCall)] fake.unchangedSinceCachedArgsForCall = append(fake.unchangedSinceCachedArgsForCall, struct { arg1 *unstructured.Unstructured - arg2 []*unstructured.Unstructured - }{arg1, arg2Copy}) + arg2 *unstructured.Unstructured + }{arg1, arg2}) stub := fake.UnchangedSinceCachedStub fakeReturns := fake.unchangedSinceCachedReturns - fake.recordInvocation("UnchangedSinceCached", []interface{}{arg1, arg2Copy}) + fake.recordInvocation("UnchangedSinceCached", []interface{}{arg1, arg2}) fake.unchangedSinceCachedMutex.Unlock() if stub != nil { return stub(arg1, arg2) @@ -95,13 +102,13 @@ func (fake *FakeRepoCache) UnchangedSinceCachedCallCount() int { return len(fake.unchangedSinceCachedArgsForCall) } -func (fake *FakeRepoCache) UnchangedSinceCachedCalls(stub func(*unstructured.Unstructured, []*unstructured.Unstructured) *unstructured.Unstructured) { +func (fake *FakeRepoCache) UnchangedSinceCachedCalls(stub func(*unstructured.Unstructured, *unstructured.Unstructured) *unstructured.Unstructured) { fake.unchangedSinceCachedMutex.Lock() defer fake.unchangedSinceCachedMutex.Unlock() fake.UnchangedSinceCachedStub = stub } -func (fake *FakeRepoCache) UnchangedSinceCachedArgsForCall(i int) (*unstructured.Unstructured, []*unstructured.Unstructured) { +func (fake *FakeRepoCache) UnchangedSinceCachedArgsForCall(i int) (*unstructured.Unstructured, *unstructured.Unstructured) { fake.unchangedSinceCachedMutex.RLock() defer fake.unchangedSinceCachedMutex.RUnlock() argsForCall := fake.unchangedSinceCachedArgsForCall[i] @@ -131,6 +138,73 @@ func (fake *FakeRepoCache) UnchangedSinceCachedReturnsOnCall(i int, result1 *uns }{result1} } +func (fake *FakeRepoCache) UnchangedSinceCachedFromList(arg1 *unstructured.Unstructured, arg2 []*unstructured.Unstructured) *unstructured.Unstructured { + var arg2Copy []*unstructured.Unstructured + if arg2 != nil { + arg2Copy = make([]*unstructured.Unstructured, len(arg2)) + copy(arg2Copy, arg2) + } + fake.unchangedSinceCachedFromListMutex.Lock() + ret, specificReturn := fake.unchangedSinceCachedFromListReturnsOnCall[len(fake.unchangedSinceCachedFromListArgsForCall)] + fake.unchangedSinceCachedFromListArgsForCall = append(fake.unchangedSinceCachedFromListArgsForCall, struct { + arg1 *unstructured.Unstructured + arg2 []*unstructured.Unstructured + }{arg1, arg2Copy}) + stub := fake.UnchangedSinceCachedFromListStub + fakeReturns := fake.unchangedSinceCachedFromListReturns + fake.recordInvocation("UnchangedSinceCachedFromList", []interface{}{arg1, arg2Copy}) + fake.unchangedSinceCachedFromListMutex.Unlock() + if stub != nil { + return stub(arg1, arg2) + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeRepoCache) UnchangedSinceCachedFromListCallCount() int { + fake.unchangedSinceCachedFromListMutex.RLock() + defer fake.unchangedSinceCachedFromListMutex.RUnlock() + return len(fake.unchangedSinceCachedFromListArgsForCall) +} + +func (fake *FakeRepoCache) UnchangedSinceCachedFromListCalls(stub func(*unstructured.Unstructured, []*unstructured.Unstructured) *unstructured.Unstructured) { + fake.unchangedSinceCachedFromListMutex.Lock() + defer fake.unchangedSinceCachedFromListMutex.Unlock() + fake.UnchangedSinceCachedFromListStub = stub +} + +func (fake *FakeRepoCache) UnchangedSinceCachedFromListArgsForCall(i int) (*unstructured.Unstructured, []*unstructured.Unstructured) { + fake.unchangedSinceCachedFromListMutex.RLock() + defer fake.unchangedSinceCachedFromListMutex.RUnlock() + argsForCall := fake.unchangedSinceCachedFromListArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *FakeRepoCache) UnchangedSinceCachedFromListReturns(result1 *unstructured.Unstructured) { + fake.unchangedSinceCachedFromListMutex.Lock() + defer fake.unchangedSinceCachedFromListMutex.Unlock() + fake.UnchangedSinceCachedFromListStub = nil + fake.unchangedSinceCachedFromListReturns = struct { + result1 *unstructured.Unstructured + }{result1} +} + +func (fake *FakeRepoCache) UnchangedSinceCachedFromListReturnsOnCall(i int, result1 *unstructured.Unstructured) { + fake.unchangedSinceCachedFromListMutex.Lock() + defer fake.unchangedSinceCachedFromListMutex.Unlock() + fake.UnchangedSinceCachedFromListStub = nil + if fake.unchangedSinceCachedFromListReturnsOnCall == nil { + fake.unchangedSinceCachedFromListReturnsOnCall = make(map[int]struct { + result1 *unstructured.Unstructured + }) + } + fake.unchangedSinceCachedFromListReturnsOnCall[i] = struct { + result1 *unstructured.Unstructured + }{result1} +} + func (fake *FakeRepoCache) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() @@ -138,6 +212,8 @@ func (fake *FakeRepoCache) Invocations() map[string][][]interface{} { defer fake.setMutex.RUnlock() fake.unchangedSinceCachedMutex.RLock() defer fake.unchangedSinceCachedMutex.RUnlock() + fake.unchangedSinceCachedFromListMutex.RLock() + defer fake.unchangedSinceCachedFromListMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/pkg/repository/repositoryfakes/fake_repository.go b/pkg/repository/repositoryfakes/fake_repository.go index 71656882e..706ca4a83 100644 --- a/pkg/repository/repositoryfakes/fake_repository.go +++ b/pkg/repository/repositoryfakes/fake_repository.go @@ -10,21 +10,34 @@ import ( v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" "sigs.k8s.io/controller-runtime/pkg/client" ) type FakeRepository struct { - EnsureObjectExistsOnClusterStub func(context.Context, *unstructured.Unstructured, bool) error - ensureObjectExistsOnClusterMutex sync.RWMutex - ensureObjectExistsOnClusterArgsForCall []struct { + EnsureImmutableObjectExistsOnClusterStub func(context.Context, *unstructured.Unstructured, map[string]string) error + ensureImmutableObjectExistsOnClusterMutex sync.RWMutex + ensureImmutableObjectExistsOnClusterArgsForCall []struct { arg1 context.Context arg2 *unstructured.Unstructured - arg3 bool + arg3 map[string]string } - ensureObjectExistsOnClusterReturns struct { + ensureImmutableObjectExistsOnClusterReturns struct { result1 error } - ensureObjectExistsOnClusterReturnsOnCall map[int]struct { + ensureImmutableObjectExistsOnClusterReturnsOnCall map[int]struct { + result1 error + } + EnsureMutableObjectExistsOnClusterStub func(context.Context, *unstructured.Unstructured) error + ensureMutableObjectExistsOnClusterMutex sync.RWMutex + ensureMutableObjectExistsOnClusterArgsForCall []struct { + arg1 context.Context + arg2 *unstructured.Unstructured + } + ensureMutableObjectExistsOnClusterReturns struct { + result1 error + } + ensureMutableObjectExistsOnClusterReturnsOnCall map[int]struct { result1 error } GetDeliverableStub func(context.Context, string, string) (*v1alpha1.Deliverable, error) @@ -180,6 +193,20 @@ type FakeRepository struct { result1 []*v1alpha1.ClusterSupplyChain result2 error } + GetUnstructuredStub func(context.Context, *unstructured.Unstructured) (*unstructured.Unstructured, error) + getUnstructuredMutex sync.RWMutex + getUnstructuredArgsForCall []struct { + arg1 context.Context + arg2 *unstructured.Unstructured + } + getUnstructuredReturns struct { + result1 *unstructured.Unstructured + result2 error + } + getUnstructuredReturnsOnCall map[int]struct { + result1 *unstructured.Unstructured + result2 error + } GetWorkloadStub func(context.Context, string, string) (*v1alpha1.Workload, error) getWorkloadMutex sync.RWMutex getWorkloadArgsForCall []struct { @@ -195,11 +222,13 @@ type FakeRepository struct { result1 *v1alpha1.Workload result2 error } - ListUnstructuredStub func(context.Context, *unstructured.Unstructured) ([]*unstructured.Unstructured, error) + ListUnstructuredStub func(context.Context, schema.GroupVersionKind, string, map[string]string) ([]*unstructured.Unstructured, error) listUnstructuredMutex sync.RWMutex listUnstructuredArgsForCall []struct { arg1 context.Context - arg2 *unstructured.Unstructured + arg2 schema.GroupVersionKind + arg3 string + arg4 map[string]string } listUnstructuredReturns struct { result1 []*unstructured.Unstructured @@ -225,18 +254,18 @@ type FakeRepository struct { invocationsMutex sync.RWMutex } -func (fake *FakeRepository) EnsureObjectExistsOnCluster(arg1 context.Context, arg2 *unstructured.Unstructured, arg3 bool) error { - fake.ensureObjectExistsOnClusterMutex.Lock() - ret, specificReturn := fake.ensureObjectExistsOnClusterReturnsOnCall[len(fake.ensureObjectExistsOnClusterArgsForCall)] - fake.ensureObjectExistsOnClusterArgsForCall = append(fake.ensureObjectExistsOnClusterArgsForCall, struct { +func (fake *FakeRepository) EnsureImmutableObjectExistsOnCluster(arg1 context.Context, arg2 *unstructured.Unstructured, arg3 map[string]string) error { + fake.ensureImmutableObjectExistsOnClusterMutex.Lock() + ret, specificReturn := fake.ensureImmutableObjectExistsOnClusterReturnsOnCall[len(fake.ensureImmutableObjectExistsOnClusterArgsForCall)] + fake.ensureImmutableObjectExistsOnClusterArgsForCall = append(fake.ensureImmutableObjectExistsOnClusterArgsForCall, struct { arg1 context.Context arg2 *unstructured.Unstructured - arg3 bool + arg3 map[string]string }{arg1, arg2, arg3}) - stub := fake.EnsureObjectExistsOnClusterStub - fakeReturns := fake.ensureObjectExistsOnClusterReturns - fake.recordInvocation("EnsureObjectExistsOnCluster", []interface{}{arg1, arg2, arg3}) - fake.ensureObjectExistsOnClusterMutex.Unlock() + stub := fake.EnsureImmutableObjectExistsOnClusterStub + fakeReturns := fake.ensureImmutableObjectExistsOnClusterReturns + fake.recordInvocation("EnsureImmutableObjectExistsOnCluster", []interface{}{arg1, arg2, arg3}) + fake.ensureImmutableObjectExistsOnClusterMutex.Unlock() if stub != nil { return stub(arg1, arg2, arg3) } @@ -246,44 +275,106 @@ func (fake *FakeRepository) EnsureObjectExistsOnCluster(arg1 context.Context, ar return fakeReturns.result1 } -func (fake *FakeRepository) EnsureObjectExistsOnClusterCallCount() int { - fake.ensureObjectExistsOnClusterMutex.RLock() - defer fake.ensureObjectExistsOnClusterMutex.RUnlock() - return len(fake.ensureObjectExistsOnClusterArgsForCall) +func (fake *FakeRepository) EnsureImmutableObjectExistsOnClusterCallCount() int { + fake.ensureImmutableObjectExistsOnClusterMutex.RLock() + defer fake.ensureImmutableObjectExistsOnClusterMutex.RUnlock() + return len(fake.ensureImmutableObjectExistsOnClusterArgsForCall) } -func (fake *FakeRepository) EnsureObjectExistsOnClusterCalls(stub func(context.Context, *unstructured.Unstructured, bool) error) { - fake.ensureObjectExistsOnClusterMutex.Lock() - defer fake.ensureObjectExistsOnClusterMutex.Unlock() - fake.EnsureObjectExistsOnClusterStub = stub +func (fake *FakeRepository) EnsureImmutableObjectExistsOnClusterCalls(stub func(context.Context, *unstructured.Unstructured, map[string]string) error) { + fake.ensureImmutableObjectExistsOnClusterMutex.Lock() + defer fake.ensureImmutableObjectExistsOnClusterMutex.Unlock() + fake.EnsureImmutableObjectExistsOnClusterStub = stub } -func (fake *FakeRepository) EnsureObjectExistsOnClusterArgsForCall(i int) (context.Context, *unstructured.Unstructured, bool) { - fake.ensureObjectExistsOnClusterMutex.RLock() - defer fake.ensureObjectExistsOnClusterMutex.RUnlock() - argsForCall := fake.ensureObjectExistsOnClusterArgsForCall[i] +func (fake *FakeRepository) EnsureImmutableObjectExistsOnClusterArgsForCall(i int) (context.Context, *unstructured.Unstructured, map[string]string) { + fake.ensureImmutableObjectExistsOnClusterMutex.RLock() + defer fake.ensureImmutableObjectExistsOnClusterMutex.RUnlock() + argsForCall := fake.ensureImmutableObjectExistsOnClusterArgsForCall[i] return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 } -func (fake *FakeRepository) EnsureObjectExistsOnClusterReturns(result1 error) { - fake.ensureObjectExistsOnClusterMutex.Lock() - defer fake.ensureObjectExistsOnClusterMutex.Unlock() - fake.EnsureObjectExistsOnClusterStub = nil - fake.ensureObjectExistsOnClusterReturns = struct { +func (fake *FakeRepository) EnsureImmutableObjectExistsOnClusterReturns(result1 error) { + fake.ensureImmutableObjectExistsOnClusterMutex.Lock() + defer fake.ensureImmutableObjectExistsOnClusterMutex.Unlock() + fake.EnsureImmutableObjectExistsOnClusterStub = nil + fake.ensureImmutableObjectExistsOnClusterReturns = struct { result1 error }{result1} } -func (fake *FakeRepository) EnsureObjectExistsOnClusterReturnsOnCall(i int, result1 error) { - fake.ensureObjectExistsOnClusterMutex.Lock() - defer fake.ensureObjectExistsOnClusterMutex.Unlock() - fake.EnsureObjectExistsOnClusterStub = nil - if fake.ensureObjectExistsOnClusterReturnsOnCall == nil { - fake.ensureObjectExistsOnClusterReturnsOnCall = make(map[int]struct { +func (fake *FakeRepository) EnsureImmutableObjectExistsOnClusterReturnsOnCall(i int, result1 error) { + fake.ensureImmutableObjectExistsOnClusterMutex.Lock() + defer fake.ensureImmutableObjectExistsOnClusterMutex.Unlock() + fake.EnsureImmutableObjectExistsOnClusterStub = nil + if fake.ensureImmutableObjectExistsOnClusterReturnsOnCall == nil { + fake.ensureImmutableObjectExistsOnClusterReturnsOnCall = make(map[int]struct { result1 error }) } - fake.ensureObjectExistsOnClusterReturnsOnCall[i] = struct { + fake.ensureImmutableObjectExistsOnClusterReturnsOnCall[i] = struct { + result1 error + }{result1} +} + +func (fake *FakeRepository) EnsureMutableObjectExistsOnCluster(arg1 context.Context, arg2 *unstructured.Unstructured) error { + fake.ensureMutableObjectExistsOnClusterMutex.Lock() + ret, specificReturn := fake.ensureMutableObjectExistsOnClusterReturnsOnCall[len(fake.ensureMutableObjectExistsOnClusterArgsForCall)] + fake.ensureMutableObjectExistsOnClusterArgsForCall = append(fake.ensureMutableObjectExistsOnClusterArgsForCall, struct { + arg1 context.Context + arg2 *unstructured.Unstructured + }{arg1, arg2}) + stub := fake.EnsureMutableObjectExistsOnClusterStub + fakeReturns := fake.ensureMutableObjectExistsOnClusterReturns + fake.recordInvocation("EnsureMutableObjectExistsOnCluster", []interface{}{arg1, arg2}) + fake.ensureMutableObjectExistsOnClusterMutex.Unlock() + if stub != nil { + return stub(arg1, arg2) + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeRepository) EnsureMutableObjectExistsOnClusterCallCount() int { + fake.ensureMutableObjectExistsOnClusterMutex.RLock() + defer fake.ensureMutableObjectExistsOnClusterMutex.RUnlock() + return len(fake.ensureMutableObjectExistsOnClusterArgsForCall) +} + +func (fake *FakeRepository) EnsureMutableObjectExistsOnClusterCalls(stub func(context.Context, *unstructured.Unstructured) error) { + fake.ensureMutableObjectExistsOnClusterMutex.Lock() + defer fake.ensureMutableObjectExistsOnClusterMutex.Unlock() + fake.EnsureMutableObjectExistsOnClusterStub = stub +} + +func (fake *FakeRepository) EnsureMutableObjectExistsOnClusterArgsForCall(i int) (context.Context, *unstructured.Unstructured) { + fake.ensureMutableObjectExistsOnClusterMutex.RLock() + defer fake.ensureMutableObjectExistsOnClusterMutex.RUnlock() + argsForCall := fake.ensureMutableObjectExistsOnClusterArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *FakeRepository) EnsureMutableObjectExistsOnClusterReturns(result1 error) { + fake.ensureMutableObjectExistsOnClusterMutex.Lock() + defer fake.ensureMutableObjectExistsOnClusterMutex.Unlock() + fake.EnsureMutableObjectExistsOnClusterStub = nil + fake.ensureMutableObjectExistsOnClusterReturns = struct { + result1 error + }{result1} +} + +func (fake *FakeRepository) EnsureMutableObjectExistsOnClusterReturnsOnCall(i int, result1 error) { + fake.ensureMutableObjectExistsOnClusterMutex.Lock() + defer fake.ensureMutableObjectExistsOnClusterMutex.Unlock() + fake.EnsureMutableObjectExistsOnClusterStub = nil + if fake.ensureMutableObjectExistsOnClusterReturnsOnCall == nil { + fake.ensureMutableObjectExistsOnClusterReturnsOnCall = make(map[int]struct { + result1 error + }) + } + fake.ensureMutableObjectExistsOnClusterReturnsOnCall[i] = struct { result1 error }{result1} } @@ -994,6 +1085,71 @@ func (fake *FakeRepository) GetSupplyChainsForWorkloadReturnsOnCall(i int, resul }{result1, result2} } +func (fake *FakeRepository) GetUnstructured(arg1 context.Context, arg2 *unstructured.Unstructured) (*unstructured.Unstructured, error) { + fake.getUnstructuredMutex.Lock() + ret, specificReturn := fake.getUnstructuredReturnsOnCall[len(fake.getUnstructuredArgsForCall)] + fake.getUnstructuredArgsForCall = append(fake.getUnstructuredArgsForCall, struct { + arg1 context.Context + arg2 *unstructured.Unstructured + }{arg1, arg2}) + stub := fake.GetUnstructuredStub + fakeReturns := fake.getUnstructuredReturns + fake.recordInvocation("GetUnstructured", []interface{}{arg1, arg2}) + fake.getUnstructuredMutex.Unlock() + if stub != nil { + return stub(arg1, arg2) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeRepository) GetUnstructuredCallCount() int { + fake.getUnstructuredMutex.RLock() + defer fake.getUnstructuredMutex.RUnlock() + return len(fake.getUnstructuredArgsForCall) +} + +func (fake *FakeRepository) GetUnstructuredCalls(stub func(context.Context, *unstructured.Unstructured) (*unstructured.Unstructured, error)) { + fake.getUnstructuredMutex.Lock() + defer fake.getUnstructuredMutex.Unlock() + fake.GetUnstructuredStub = stub +} + +func (fake *FakeRepository) GetUnstructuredArgsForCall(i int) (context.Context, *unstructured.Unstructured) { + fake.getUnstructuredMutex.RLock() + defer fake.getUnstructuredMutex.RUnlock() + argsForCall := fake.getUnstructuredArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *FakeRepository) GetUnstructuredReturns(result1 *unstructured.Unstructured, result2 error) { + fake.getUnstructuredMutex.Lock() + defer fake.getUnstructuredMutex.Unlock() + fake.GetUnstructuredStub = nil + fake.getUnstructuredReturns = struct { + result1 *unstructured.Unstructured + result2 error + }{result1, result2} +} + +func (fake *FakeRepository) GetUnstructuredReturnsOnCall(i int, result1 *unstructured.Unstructured, result2 error) { + fake.getUnstructuredMutex.Lock() + defer fake.getUnstructuredMutex.Unlock() + fake.GetUnstructuredStub = nil + if fake.getUnstructuredReturnsOnCall == nil { + fake.getUnstructuredReturnsOnCall = make(map[int]struct { + result1 *unstructured.Unstructured + result2 error + }) + } + fake.getUnstructuredReturnsOnCall[i] = struct { + result1 *unstructured.Unstructured + result2 error + }{result1, result2} +} + func (fake *FakeRepository) GetWorkload(arg1 context.Context, arg2 string, arg3 string) (*v1alpha1.Workload, error) { fake.getWorkloadMutex.Lock() ret, specificReturn := fake.getWorkloadReturnsOnCall[len(fake.getWorkloadArgsForCall)] @@ -1060,19 +1216,21 @@ func (fake *FakeRepository) GetWorkloadReturnsOnCall(i int, result1 *v1alpha1.Wo }{result1, result2} } -func (fake *FakeRepository) ListUnstructured(arg1 context.Context, arg2 *unstructured.Unstructured) ([]*unstructured.Unstructured, error) { +func (fake *FakeRepository) ListUnstructured(arg1 context.Context, arg2 schema.GroupVersionKind, arg3 string, arg4 map[string]string) ([]*unstructured.Unstructured, error) { fake.listUnstructuredMutex.Lock() ret, specificReturn := fake.listUnstructuredReturnsOnCall[len(fake.listUnstructuredArgsForCall)] fake.listUnstructuredArgsForCall = append(fake.listUnstructuredArgsForCall, struct { arg1 context.Context - arg2 *unstructured.Unstructured - }{arg1, arg2}) + arg2 schema.GroupVersionKind + arg3 string + arg4 map[string]string + }{arg1, arg2, arg3, arg4}) stub := fake.ListUnstructuredStub fakeReturns := fake.listUnstructuredReturns - fake.recordInvocation("ListUnstructured", []interface{}{arg1, arg2}) + fake.recordInvocation("ListUnstructured", []interface{}{arg1, arg2, arg3, arg4}) fake.listUnstructuredMutex.Unlock() if stub != nil { - return stub(arg1, arg2) + return stub(arg1, arg2, arg3, arg4) } if specificReturn { return ret.result1, ret.result2 @@ -1086,17 +1244,17 @@ func (fake *FakeRepository) ListUnstructuredCallCount() int { return len(fake.listUnstructuredArgsForCall) } -func (fake *FakeRepository) ListUnstructuredCalls(stub func(context.Context, *unstructured.Unstructured) ([]*unstructured.Unstructured, error)) { +func (fake *FakeRepository) ListUnstructuredCalls(stub func(context.Context, schema.GroupVersionKind, string, map[string]string) ([]*unstructured.Unstructured, error)) { fake.listUnstructuredMutex.Lock() defer fake.listUnstructuredMutex.Unlock() fake.ListUnstructuredStub = stub } -func (fake *FakeRepository) ListUnstructuredArgsForCall(i int) (context.Context, *unstructured.Unstructured) { +func (fake *FakeRepository) ListUnstructuredArgsForCall(i int) (context.Context, schema.GroupVersionKind, string, map[string]string) { fake.listUnstructuredMutex.RLock() defer fake.listUnstructuredMutex.RUnlock() argsForCall := fake.listUnstructuredArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4 } func (fake *FakeRepository) ListUnstructuredReturns(result1 []*unstructured.Unstructured, result2 error) { @@ -1190,8 +1348,10 @@ func (fake *FakeRepository) StatusUpdateReturnsOnCall(i int, result1 error) { func (fake *FakeRepository) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() - fake.ensureObjectExistsOnClusterMutex.RLock() - defer fake.ensureObjectExistsOnClusterMutex.RUnlock() + fake.ensureImmutableObjectExistsOnClusterMutex.RLock() + defer fake.ensureImmutableObjectExistsOnClusterMutex.RUnlock() + fake.ensureMutableObjectExistsOnClusterMutex.RLock() + defer fake.ensureMutableObjectExistsOnClusterMutex.RUnlock() fake.getDeliverableMutex.RLock() defer fake.getDeliverableMutex.RUnlock() fake.getDeliveriesForDeliverableMutex.RLock() @@ -1214,6 +1374,8 @@ func (fake *FakeRepository) Invocations() map[string][][]interface{} { defer fake.getSupplyChainTemplateMutex.RUnlock() fake.getSupplyChainsForWorkloadMutex.RLock() defer fake.getSupplyChainsForWorkloadMutex.RUnlock() + fake.getUnstructuredMutex.RLock() + defer fake.getUnstructuredMutex.RUnlock() fake.getWorkloadMutex.RLock() defer fake.getWorkloadMutex.RUnlock() fake.listUnstructuredMutex.RLock()