From b444a91658cb640d5272c37360a85ded41148eaf Mon Sep 17 00:00:00 2001 From: Frederic Giloux Date: Tue, 14 Dec 2021 13:39:24 +0100 Subject: [PATCH] Extend Step to surface whether a manifest is optional Signed-off-by: Frederic Giloux --- go.mod | 2 +- go.sum | 4 +- pkg/controller/operators/catalog/manifests.go | 2 +- pkg/controller/operators/catalog/operator.go | 40 +- .../operators/catalog/operator_test.go | 112 +++ .../fakes/fake_registry_client_interface.go | 847 ++++++++++++++++++ .../registry/resolver/step_resolver_test.go | 2 +- pkg/controller/registry/resolver/steps.go | 118 ++- .../registry/resolver/steps_test.go | 48 + .../api/pkg/encoding/encoding.go | 50 ++ .../api/pkg/manifests/bundle.go | 4 + .../api/pkg/manifests/bundleloader.go | 36 + .../api/pkg/validation/errors/error.go | 2 +- .../api/pkg/validation/internal/bundle.go | 68 +- .../pkg/validation/internal/removed_apis.go | 15 + vendor/modules.txt | 3 +- 16 files changed, 1324 insertions(+), 29 deletions(-) create mode 100644 pkg/controller/registry/resolver/fakes/fake_registry_client_interface.go create mode 100644 pkg/controller/registry/resolver/steps_test.go create mode 100644 vendor/github.com/operator-framework/api/pkg/encoding/encoding.go diff --git a/go.mod b/go.mod index c8552e99b16..1c7f66ab2bc 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/onsi/gomega v1.17.0 github.com/openshift/api v0.0.0-20200331152225-585af27e34fd github.com/openshift/client-go v0.0.0-20200326155132-2a6cd50aedd0 - github.com/operator-framework/api v0.11.2-0.20220128160316-8e593f1c42b9 + github.com/operator-framework/api v0.14.0 github.com/operator-framework/operator-registry v1.17.5 github.com/otiai10/copy v1.2.0 github.com/pkg/errors v0.9.1 diff --git a/go.sum b/go.sum index 7083913c1d6..c785404bf23 100644 --- a/go.sum +++ b/go.sum @@ -934,8 +934,8 @@ github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJ github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/operator-framework/api v0.7.1/go.mod h1:L7IvLd/ckxJEJg/t4oTTlnHKAJIP/p51AvEslW3wYdY= -github.com/operator-framework/api v0.11.2-0.20220128160316-8e593f1c42b9 h1:aOF1+e6amntViIbc9BrpD07cwmgcVFG4wxPbj/VGCjA= -github.com/operator-framework/api v0.11.2-0.20220128160316-8e593f1c42b9/go.mod h1:r/erkmp9Kc1Al4dnxmRkJYc0uCtD5FohN9VuJ5nTxz0= +github.com/operator-framework/api v0.14.0 h1:5nk8fQL8l+dDxi11hZi0T7nqhhoIQLn+qL2DhMEGnoE= +github.com/operator-framework/api v0.14.0/go.mod h1:r/erkmp9Kc1Al4dnxmRkJYc0uCtD5FohN9VuJ5nTxz0= github.com/operator-framework/operator-registry v1.17.5 h1:LR8m1rFz5Gcyje8WK6iYt+gIhtzqo52zMRALdmTYHT0= github.com/operator-framework/operator-registry v1.17.5/go.mod h1:sRQIgDMZZdUcmHltzyCnM6RUoDF+WS8Arj1BQIARDS8= github.com/otiai10/copy v1.2.0 h1:HvG945u96iNadPoG2/Ja2+AUJeW5YuFQMixq9yirC+k= diff --git a/pkg/controller/operators/catalog/manifests.go b/pkg/controller/operators/catalog/manifests.go index 9e8f12af511..f8c7495fe52 100644 --- a/pkg/controller/operators/catalog/manifests.go +++ b/pkg/controller/operators/catalog/manifests.go @@ -95,7 +95,7 @@ func (r *manifestResolver) unpackedStepsForBundle(bundleName string, ref *Unpack bundle.Properties = props } - steps, err := resolver.NewStepResourceFromBundle(bundle, r.namespace, ref.Replaces, ref.CatalogSourceName, ref.CatalogSourceNamespace) + steps, _, err := resolver.NewStepResourceFromBundle(bundle, r.namespace, ref.Replaces, ref.CatalogSourceName, ref.CatalogSourceNamespace) if err != nil { return nil, errorwrap.Wrapf(err, "error calculating steps for ref %v", *ref) } diff --git a/pkg/controller/operators/catalog/operator.go b/pkg/controller/operators/catalog/operator.go index 71ab352dfdb..d2cf8cecc78 100644 --- a/pkg/controller/operators/catalog/operator.go +++ b/pkg/controller/operators/catalog/operator.go @@ -1392,11 +1392,12 @@ func (o *Operator) unpackBundles(plan *v1alpha1.InstallPlan) (bool, *v1alpha1.In // if packed condition is missing, bundle has already been unpacked into steps, continue if res.GetCondition(resolver.BundleLookupConditionPacked).Status == corev1.ConditionUnknown { + o.logger.Debug("Bundle already unpacked") continue } // Ensure that bundle can be applied by the current version of OLM by converting to steps - steps, err := resolver.NewStepsFromBundle(res.Bundle(), out.GetNamespace(), res.Replaces, res.CatalogSourceRef.Name, res.CatalogSourceRef.Namespace) + steps, err := resolver.NewQualifiedStepsFromBundle(res.Bundle(), out.GetNamespace(), res.Replaces, res.CatalogSourceRef.Name, res.CatalogSourceRef.Namespace, o.logger) if err != nil { errs = append(errs, fmt.Errorf("failed to turn bundle into steps: %v", err)) unpacked = false @@ -1590,7 +1591,6 @@ func (o *Operator) syncInstallPlans(obj interface{}) (syncError error) { syncError = fmt.Errorf("bundle unpacking failed: %v", err) return } - if !reflect.DeepEqual(plan.Status, out.Status) { logger.Warnf("status not equal, updating...") if _, err := o.client.OperatorsV1alpha1().InstallPlans(out.GetNamespace()).UpdateStatus(context.TODO(), out, metav1.UpdateOptions{}); err != nil { @@ -1631,7 +1631,6 @@ func (o *Operator) syncInstallPlans(obj interface{}) (syncError error) { return } } - outInstallPlan, syncError := transitionInstallPlanState(logger.Logger, o, *plan, o.now(), o.installPlanTimeout) if syncError != nil { @@ -1983,7 +1982,7 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error { } switch step.Status { - case v1alpha1.StepStatusPresent, v1alpha1.StepStatusCreated, v1alpha1.StepStatusWaitingForAPI: + case v1alpha1.StepStatusPresent, v1alpha1.StepStatusCreated, v1alpha1.StepStatusWaitingForAPI, v1alpha1.StepStatusNotCreated: return nil case v1alpha1.StepStatusUnknown, v1alpha1.StepStatusNotPresent: manifest, err := r.ManifestForStep(step) @@ -2252,6 +2251,7 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error { if !isSupported(step.Resource.Kind) { // Not a supported resource plan.Status.Plan[i].Status = v1alpha1.StepStatusUnsupportedResource + o.logger.WithError(err).Debug("resource kind not supported") return v1alpha1.ErrInvalidInstallPlan } @@ -2266,7 +2266,14 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error { gvk := unstructuredObject.GroupVersionKind() r, err := o.apiresourceFromGVK(gvk) if err != nil { - return err + if step.Optional { + // Special handling of the unavailability of the API for optional resources: no error + o.logger.WithFields(logrus.Fields{"InstallPlan": plan.Name, "kind": step.Resource.Kind, "name": step.Resource.Name}).Warnf("Optional manifest could not be created. Optional capabilities of the operator may not be available.") + plan.Status.Plan[i].Status = v1alpha1.StepStatusNotCreated + return nil + } else { + return err + } } // Create the GVR @@ -2312,7 +2319,26 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error { // Ensure Unstructured Object status, err := ensurer.EnsureUnstructuredObject(resourceInterface, unstructuredObject) if err != nil { - return err + if step.Optional { + switch k8serrors.ReasonForError(err) { + case + metav1.StatusReasonUnauthorized, + metav1.StatusReasonForbidden, + metav1.StatusReasonNotFound, + metav1.StatusReasonInvalid, + metav1.StatusReasonNotAcceptable, + metav1.StatusReasonUnsupportedMediaType, + metav1.StatusReasonConflict: + o.logger.WithFields(logrus.Fields{"kind": step.Resource.Kind, "name": step.Resource.Name}).WithError(err).Warnf("Optional manifest could not be created. Optional capabilities of the operator may not be available.") + status = v1alpha1.StepStatusNotCreated + // no error + default: + o.logger.WithFields(logrus.Fields{"kind": step.Resource.Kind, "name": step.Resource.Name}).WithError(err).Warnf("Optional manifest could not be created, possibly due to a recoverable error, retrying") + return err + } + } else { + return err + } } plan.Status.Plan[i].Status = status @@ -2337,7 +2363,7 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error { // Loop over one final time to check and see if everything is good. for _, step := range plan.Status.Plan { switch step.Status { - case v1alpha1.StepStatusCreated, v1alpha1.StepStatusPresent: + case v1alpha1.StepStatusCreated, v1alpha1.StepStatusPresent, v1alpha1.StepStatusNotCreated: default: return nil } diff --git a/pkg/controller/operators/catalog/operator_test.go b/pkg/controller/operators/catalog/operator_test.go index 5518bf1d3da..cc11366095f 100644 --- a/pkg/controller/operators/catalog/operator_test.go +++ b/pkg/controller/operators/catalog/operator_test.go @@ -604,6 +604,118 @@ func TestExecutePlan(t *testing.T) { }, }, }, + { + testName: "OptionalManifestWithCRD", + in: withSteps(installPlan("p", namespace, v1alpha1.InstallPlanPhaseInstalling, "csv"), + []*v1alpha1.Step{ + { + Resolving: "csv", + Resource: v1alpha1.StepResource{ + CatalogSource: "catalog", + CatalogSourceNamespace: namespace, + Group: "operators.coreos.com", + Version: "v1alpha1", + Kind: "ClusterServiceVersion", + Name: "csv", + Manifest: toManifest(t, csv("csv", namespace, nil, nil)), + }, + Status: v1alpha1.StepStatusUnknown, + }, + { + Resolving: "csv", + Optional: true, + Resource: v1alpha1.StepResource{ + CatalogSource: "catalog", + CatalogSourceNamespace: namespace, + Group: "monitoring.coreos.com", + Version: "v1", + Kind: "PrometheusRule", + Name: "rule", + Manifest: toManifest(t, decodeFile(t, "./testdata/prometheusrule.cr.yaml", &unstructured.Unstructured{})), + }, + Status: v1alpha1.StepStatusUnknown, + }, + }, + ), + extObjs: []runtime.Object{decodeFile(t, "./testdata/prometheusrule.crd.yaml", &apiextensionsv1beta1.CustomResourceDefinition{})}, + want: []runtime.Object{ + csv("csv", namespace, nil, nil), + modify(t, decodeFile(t, "./testdata/prometheusrule.cr.yaml", &unstructured.Unstructured{}), + withNamespace(namespace), + withOwner(csv("csv", namespace, nil, nil)), + ), + }, + err: nil, + }, + { + testName: "NotOptionalManifestWithoutCRD", + in: withSteps(installPlan("p", namespace, v1alpha1.InstallPlanPhaseInstalling, "csv"), + []*v1alpha1.Step{ + { + Resolving: "csv", + Resource: v1alpha1.StepResource{ + CatalogSource: "catalog", + CatalogSourceNamespace: namespace, + Group: "operators.coreos.com", + Version: "v1alpha1", + Kind: "ClusterServiceVersion", + Name: "csv", + Manifest: toManifest(t, csv("csv", namespace, nil, nil)), + }, + Status: v1alpha1.StepStatusUnknown, + }, + { + Resolving: "csv", + Resource: v1alpha1.StepResource{ + CatalogSource: "catalog", + CatalogSourceNamespace: namespace, + Group: "monitoring.coreos.com", + Version: "v1", + Kind: "PrometheusRule", + Name: "rule", + Manifest: toManifest(t, decodeFile(t, "./testdata/prometheusrule.cr.yaml", &unstructured.Unstructured{})), + }, + Status: v1alpha1.StepStatusUnknown, + }, + }, + ), + err: gvkNotFoundError{gvk: schema.GroupVersionKind{Group: "monitoring.coreos.com", Version: "v1", Kind: "PrometheusRule"}, name: "rule"}, + }, + { + testName: "OptionalManifestWithoutCRD", + in: withSteps(installPlan("p", namespace, v1alpha1.InstallPlanPhaseInstalling, "csv"), + []*v1alpha1.Step{ + { + Resolving: "csv", + Resource: v1alpha1.StepResource{ + CatalogSource: "catalog", + CatalogSourceNamespace: namespace, + Group: "operators.coreos.com", + Version: "v1alpha1", + Kind: "ClusterServiceVersion", + Name: "csv", + Manifest: toManifest(t, csv("csv", namespace, nil, nil)), + }, + Status: v1alpha1.StepStatusUnknown, + }, + { + Resolving: "csv", + Optional: true, + Resource: v1alpha1.StepResource{ + CatalogSource: "catalog", + CatalogSourceNamespace: namespace, + Group: "monitoring.coreos.com", + Version: "v1", + Kind: "PrometheusRule", + Name: "rule", + Manifest: toManifest(t, decodeFile(t, "./testdata/prometheusrule.cr.yaml", &unstructured.Unstructured{})), + }, + Status: v1alpha1.StepStatusUnknown, + }, + }, + ), + err: nil, + }, } for _, tt := range tests { diff --git a/pkg/controller/registry/resolver/fakes/fake_registry_client_interface.go b/pkg/controller/registry/resolver/fakes/fake_registry_client_interface.go new file mode 100644 index 00000000000..48a508baaca --- /dev/null +++ b/pkg/controller/registry/resolver/fakes/fake_registry_client_interface.go @@ -0,0 +1,847 @@ +// Code generated by counterfeiter. DO NOT EDIT. +package fakes + +import ( + "context" + "sync" + "time" + + "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry" + "github.com/operator-framework/operator-registry/pkg/api" + "github.com/operator-framework/operator-registry/pkg/client" +) + +type FakeClientInterface struct { + CloseStub func() error + closeMutex sync.RWMutex + closeArgsForCall []struct { + } + closeReturns struct { + result1 error + } + closeReturnsOnCall map[int]struct { + result1 error + } + FindBundleThatProvidesStub func(context.Context, string, string, string, map[string]struct{}) (*api.Bundle, error) + findBundleThatProvidesMutex sync.RWMutex + findBundleThatProvidesArgsForCall []struct { + arg1 context.Context + arg2 string + arg3 string + arg4 string + arg5 map[string]struct{} + } + findBundleThatProvidesReturns struct { + result1 *api.Bundle + result2 error + } + findBundleThatProvidesReturnsOnCall map[int]struct { + result1 *api.Bundle + result2 error + } + GetBundleStub func(context.Context, string, string, string) (*api.Bundle, error) + getBundleMutex sync.RWMutex + getBundleArgsForCall []struct { + arg1 context.Context + arg2 string + arg3 string + arg4 string + } + getBundleReturns struct { + result1 *api.Bundle + result2 error + } + getBundleReturnsOnCall map[int]struct { + result1 *api.Bundle + result2 error + } + GetBundleInPackageChannelStub func(context.Context, string, string) (*api.Bundle, error) + getBundleInPackageChannelMutex sync.RWMutex + getBundleInPackageChannelArgsForCall []struct { + arg1 context.Context + arg2 string + arg3 string + } + getBundleInPackageChannelReturns struct { + result1 *api.Bundle + result2 error + } + getBundleInPackageChannelReturnsOnCall map[int]struct { + result1 *api.Bundle + result2 error + } + GetBundleThatProvidesStub func(context.Context, string, string, string) (*api.Bundle, error) + getBundleThatProvidesMutex sync.RWMutex + getBundleThatProvidesArgsForCall []struct { + arg1 context.Context + arg2 string + arg3 string + arg4 string + } + getBundleThatProvidesReturns struct { + result1 *api.Bundle + result2 error + } + getBundleThatProvidesReturnsOnCall map[int]struct { + result1 *api.Bundle + result2 error + } + GetLatestChannelEntriesThatProvideStub func(context.Context, string, string, string) (*registry.ChannelEntryIterator, error) + getLatestChannelEntriesThatProvideMutex sync.RWMutex + getLatestChannelEntriesThatProvideArgsForCall []struct { + arg1 context.Context + arg2 string + arg3 string + arg4 string + } + getLatestChannelEntriesThatProvideReturns struct { + result1 *registry.ChannelEntryIterator + result2 error + } + getLatestChannelEntriesThatProvideReturnsOnCall map[int]struct { + result1 *registry.ChannelEntryIterator + result2 error + } + GetPackageStub func(context.Context, string) (*api.Package, error) + getPackageMutex sync.RWMutex + getPackageArgsForCall []struct { + arg1 context.Context + arg2 string + } + getPackageReturns struct { + result1 *api.Package + result2 error + } + getPackageReturnsOnCall map[int]struct { + result1 *api.Package + result2 error + } + GetReplacementBundleInPackageChannelStub func(context.Context, string, string, string) (*api.Bundle, error) + getReplacementBundleInPackageChannelMutex sync.RWMutex + getReplacementBundleInPackageChannelArgsForCall []struct { + arg1 context.Context + arg2 string + arg3 string + arg4 string + } + getReplacementBundleInPackageChannelReturns struct { + result1 *api.Bundle + result2 error + } + getReplacementBundleInPackageChannelReturnsOnCall map[int]struct { + result1 *api.Bundle + result2 error + } + HealthCheckStub func(context.Context, time.Duration) (bool, error) + healthCheckMutex sync.RWMutex + healthCheckArgsForCall []struct { + arg1 context.Context + arg2 time.Duration + } + healthCheckReturns struct { + result1 bool + result2 error + } + healthCheckReturnsOnCall map[int]struct { + result1 bool + result2 error + } + ListBundlesStub func(context.Context) (*client.BundleIterator, error) + listBundlesMutex sync.RWMutex + listBundlesArgsForCall []struct { + arg1 context.Context + } + listBundlesReturns struct { + result1 *client.BundleIterator + result2 error + } + listBundlesReturnsOnCall map[int]struct { + result1 *client.BundleIterator + result2 error + } + invocations map[string][][]interface{} + invocationsMutex sync.RWMutex +} + +func (fake *FakeClientInterface) Close() error { + fake.closeMutex.Lock() + ret, specificReturn := fake.closeReturnsOnCall[len(fake.closeArgsForCall)] + fake.closeArgsForCall = append(fake.closeArgsForCall, struct { + }{}) + fake.recordInvocation("Close", []interface{}{}) + fake.closeMutex.Unlock() + if fake.CloseStub != nil { + return fake.CloseStub() + } + if specificReturn { + return ret.result1 + } + fakeReturns := fake.closeReturns + return fakeReturns.result1 +} + +func (fake *FakeClientInterface) CloseCallCount() int { + fake.closeMutex.RLock() + defer fake.closeMutex.RUnlock() + return len(fake.closeArgsForCall) +} + +func (fake *FakeClientInterface) CloseCalls(stub func() error) { + fake.closeMutex.Lock() + defer fake.closeMutex.Unlock() + fake.CloseStub = stub +} + +func (fake *FakeClientInterface) CloseReturns(result1 error) { + fake.closeMutex.Lock() + defer fake.closeMutex.Unlock() + fake.CloseStub = nil + fake.closeReturns = struct { + result1 error + }{result1} +} + +func (fake *FakeClientInterface) CloseReturnsOnCall(i int, result1 error) { + fake.closeMutex.Lock() + defer fake.closeMutex.Unlock() + fake.CloseStub = nil + if fake.closeReturnsOnCall == nil { + fake.closeReturnsOnCall = make(map[int]struct { + result1 error + }) + } + fake.closeReturnsOnCall[i] = struct { + result1 error + }{result1} +} + +func (fake *FakeClientInterface) FindBundleThatProvides(arg1 context.Context, arg2 string, arg3 string, arg4 string, arg5 map[string]struct{}) (*api.Bundle, error) { + fake.findBundleThatProvidesMutex.Lock() + ret, specificReturn := fake.findBundleThatProvidesReturnsOnCall[len(fake.findBundleThatProvidesArgsForCall)] + fake.findBundleThatProvidesArgsForCall = append(fake.findBundleThatProvidesArgsForCall, struct { + arg1 context.Context + arg2 string + arg3 string + arg4 string + arg5 map[string]struct{} + }{arg1, arg2, arg3, arg4, arg5}) + fake.recordInvocation("FindBundleThatProvides", []interface{}{arg1, arg2, arg3, arg4, arg5}) + fake.findBundleThatProvidesMutex.Unlock() + if fake.FindBundleThatProvidesStub != nil { + return fake.FindBundleThatProvidesStub(arg1, arg2, arg3, arg4, arg5) + } + if specificReturn { + return ret.result1, ret.result2 + } + fakeReturns := fake.findBundleThatProvidesReturns + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeClientInterface) FindBundleThatProvidesCallCount() int { + fake.findBundleThatProvidesMutex.RLock() + defer fake.findBundleThatProvidesMutex.RUnlock() + return len(fake.findBundleThatProvidesArgsForCall) +} + +func (fake *FakeClientInterface) FindBundleThatProvidesCalls(stub func(context.Context, string, string, string, map[string]struct{}) (*api.Bundle, error)) { + fake.findBundleThatProvidesMutex.Lock() + defer fake.findBundleThatProvidesMutex.Unlock() + fake.FindBundleThatProvidesStub = stub +} + +func (fake *FakeClientInterface) FindBundleThatProvidesArgsForCall(i int) (context.Context, string, string, string, map[string]struct{}) { + fake.findBundleThatProvidesMutex.RLock() + defer fake.findBundleThatProvidesMutex.RUnlock() + argsForCall := fake.findBundleThatProvidesArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4, argsForCall.arg5 +} + +func (fake *FakeClientInterface) FindBundleThatProvidesReturns(result1 *api.Bundle, result2 error) { + fake.findBundleThatProvidesMutex.Lock() + defer fake.findBundleThatProvidesMutex.Unlock() + fake.FindBundleThatProvidesStub = nil + fake.findBundleThatProvidesReturns = struct { + result1 *api.Bundle + result2 error + }{result1, result2} +} + +func (fake *FakeClientInterface) FindBundleThatProvidesReturnsOnCall(i int, result1 *api.Bundle, result2 error) { + fake.findBundleThatProvidesMutex.Lock() + defer fake.findBundleThatProvidesMutex.Unlock() + fake.FindBundleThatProvidesStub = nil + if fake.findBundleThatProvidesReturnsOnCall == nil { + fake.findBundleThatProvidesReturnsOnCall = make(map[int]struct { + result1 *api.Bundle + result2 error + }) + } + fake.findBundleThatProvidesReturnsOnCall[i] = struct { + result1 *api.Bundle + result2 error + }{result1, result2} +} + +func (fake *FakeClientInterface) GetBundle(arg1 context.Context, arg2 string, arg3 string, arg4 string) (*api.Bundle, error) { + fake.getBundleMutex.Lock() + ret, specificReturn := fake.getBundleReturnsOnCall[len(fake.getBundleArgsForCall)] + fake.getBundleArgsForCall = append(fake.getBundleArgsForCall, struct { + arg1 context.Context + arg2 string + arg3 string + arg4 string + }{arg1, arg2, arg3, arg4}) + fake.recordInvocation("GetBundle", []interface{}{arg1, arg2, arg3, arg4}) + fake.getBundleMutex.Unlock() + if fake.GetBundleStub != nil { + return fake.GetBundleStub(arg1, arg2, arg3, arg4) + } + if specificReturn { + return ret.result1, ret.result2 + } + fakeReturns := fake.getBundleReturns + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeClientInterface) GetBundleCallCount() int { + fake.getBundleMutex.RLock() + defer fake.getBundleMutex.RUnlock() + return len(fake.getBundleArgsForCall) +} + +func (fake *FakeClientInterface) GetBundleCalls(stub func(context.Context, string, string, string) (*api.Bundle, error)) { + fake.getBundleMutex.Lock() + defer fake.getBundleMutex.Unlock() + fake.GetBundleStub = stub +} + +func (fake *FakeClientInterface) GetBundleArgsForCall(i int) (context.Context, string, string, string) { + fake.getBundleMutex.RLock() + defer fake.getBundleMutex.RUnlock() + argsForCall := fake.getBundleArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4 +} + +func (fake *FakeClientInterface) GetBundleReturns(result1 *api.Bundle, result2 error) { + fake.getBundleMutex.Lock() + defer fake.getBundleMutex.Unlock() + fake.GetBundleStub = nil + fake.getBundleReturns = struct { + result1 *api.Bundle + result2 error + }{result1, result2} +} + +func (fake *FakeClientInterface) GetBundleReturnsOnCall(i int, result1 *api.Bundle, result2 error) { + fake.getBundleMutex.Lock() + defer fake.getBundleMutex.Unlock() + fake.GetBundleStub = nil + if fake.getBundleReturnsOnCall == nil { + fake.getBundleReturnsOnCall = make(map[int]struct { + result1 *api.Bundle + result2 error + }) + } + fake.getBundleReturnsOnCall[i] = struct { + result1 *api.Bundle + result2 error + }{result1, result2} +} + +func (fake *FakeClientInterface) GetBundleInPackageChannel(arg1 context.Context, arg2 string, arg3 string) (*api.Bundle, error) { + fake.getBundleInPackageChannelMutex.Lock() + ret, specificReturn := fake.getBundleInPackageChannelReturnsOnCall[len(fake.getBundleInPackageChannelArgsForCall)] + fake.getBundleInPackageChannelArgsForCall = append(fake.getBundleInPackageChannelArgsForCall, struct { + arg1 context.Context + arg2 string + arg3 string + }{arg1, arg2, arg3}) + fake.recordInvocation("GetBundleInPackageChannel", []interface{}{arg1, arg2, arg3}) + fake.getBundleInPackageChannelMutex.Unlock() + if fake.GetBundleInPackageChannelStub != nil { + return fake.GetBundleInPackageChannelStub(arg1, arg2, arg3) + } + if specificReturn { + return ret.result1, ret.result2 + } + fakeReturns := fake.getBundleInPackageChannelReturns + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeClientInterface) GetBundleInPackageChannelCallCount() int { + fake.getBundleInPackageChannelMutex.RLock() + defer fake.getBundleInPackageChannelMutex.RUnlock() + return len(fake.getBundleInPackageChannelArgsForCall) +} + +func (fake *FakeClientInterface) GetBundleInPackageChannelCalls(stub func(context.Context, string, string) (*api.Bundle, error)) { + fake.getBundleInPackageChannelMutex.Lock() + defer fake.getBundleInPackageChannelMutex.Unlock() + fake.GetBundleInPackageChannelStub = stub +} + +func (fake *FakeClientInterface) GetBundleInPackageChannelArgsForCall(i int) (context.Context, string, string) { + fake.getBundleInPackageChannelMutex.RLock() + defer fake.getBundleInPackageChannelMutex.RUnlock() + argsForCall := fake.getBundleInPackageChannelArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 +} + +func (fake *FakeClientInterface) GetBundleInPackageChannelReturns(result1 *api.Bundle, result2 error) { + fake.getBundleInPackageChannelMutex.Lock() + defer fake.getBundleInPackageChannelMutex.Unlock() + fake.GetBundleInPackageChannelStub = nil + fake.getBundleInPackageChannelReturns = struct { + result1 *api.Bundle + result2 error + }{result1, result2} +} + +func (fake *FakeClientInterface) GetBundleInPackageChannelReturnsOnCall(i int, result1 *api.Bundle, result2 error) { + fake.getBundleInPackageChannelMutex.Lock() + defer fake.getBundleInPackageChannelMutex.Unlock() + fake.GetBundleInPackageChannelStub = nil + if fake.getBundleInPackageChannelReturnsOnCall == nil { + fake.getBundleInPackageChannelReturnsOnCall = make(map[int]struct { + result1 *api.Bundle + result2 error + }) + } + fake.getBundleInPackageChannelReturnsOnCall[i] = struct { + result1 *api.Bundle + result2 error + }{result1, result2} +} + +func (fake *FakeClientInterface) GetBundleThatProvides(arg1 context.Context, arg2 string, arg3 string, arg4 string) (*api.Bundle, error) { + fake.getBundleThatProvidesMutex.Lock() + ret, specificReturn := fake.getBundleThatProvidesReturnsOnCall[len(fake.getBundleThatProvidesArgsForCall)] + fake.getBundleThatProvidesArgsForCall = append(fake.getBundleThatProvidesArgsForCall, struct { + arg1 context.Context + arg2 string + arg3 string + arg4 string + }{arg1, arg2, arg3, arg4}) + fake.recordInvocation("GetBundleThatProvides", []interface{}{arg1, arg2, arg3, arg4}) + fake.getBundleThatProvidesMutex.Unlock() + if fake.GetBundleThatProvidesStub != nil { + return fake.GetBundleThatProvidesStub(arg1, arg2, arg3, arg4) + } + if specificReturn { + return ret.result1, ret.result2 + } + fakeReturns := fake.getBundleThatProvidesReturns + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeClientInterface) GetBundleThatProvidesCallCount() int { + fake.getBundleThatProvidesMutex.RLock() + defer fake.getBundleThatProvidesMutex.RUnlock() + return len(fake.getBundleThatProvidesArgsForCall) +} + +func (fake *FakeClientInterface) GetBundleThatProvidesCalls(stub func(context.Context, string, string, string) (*api.Bundle, error)) { + fake.getBundleThatProvidesMutex.Lock() + defer fake.getBundleThatProvidesMutex.Unlock() + fake.GetBundleThatProvidesStub = stub +} + +func (fake *FakeClientInterface) GetBundleThatProvidesArgsForCall(i int) (context.Context, string, string, string) { + fake.getBundleThatProvidesMutex.RLock() + defer fake.getBundleThatProvidesMutex.RUnlock() + argsForCall := fake.getBundleThatProvidesArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4 +} + +func (fake *FakeClientInterface) GetBundleThatProvidesReturns(result1 *api.Bundle, result2 error) { + fake.getBundleThatProvidesMutex.Lock() + defer fake.getBundleThatProvidesMutex.Unlock() + fake.GetBundleThatProvidesStub = nil + fake.getBundleThatProvidesReturns = struct { + result1 *api.Bundle + result2 error + }{result1, result2} +} + +func (fake *FakeClientInterface) GetBundleThatProvidesReturnsOnCall(i int, result1 *api.Bundle, result2 error) { + fake.getBundleThatProvidesMutex.Lock() + defer fake.getBundleThatProvidesMutex.Unlock() + fake.GetBundleThatProvidesStub = nil + if fake.getBundleThatProvidesReturnsOnCall == nil { + fake.getBundleThatProvidesReturnsOnCall = make(map[int]struct { + result1 *api.Bundle + result2 error + }) + } + fake.getBundleThatProvidesReturnsOnCall[i] = struct { + result1 *api.Bundle + result2 error + }{result1, result2} +} + +func (fake *FakeClientInterface) GetLatestChannelEntriesThatProvide(arg1 context.Context, arg2 string, arg3 string, arg4 string) (*registry.ChannelEntryIterator, error) { + fake.getLatestChannelEntriesThatProvideMutex.Lock() + ret, specificReturn := fake.getLatestChannelEntriesThatProvideReturnsOnCall[len(fake.getLatestChannelEntriesThatProvideArgsForCall)] + fake.getLatestChannelEntriesThatProvideArgsForCall = append(fake.getLatestChannelEntriesThatProvideArgsForCall, struct { + arg1 context.Context + arg2 string + arg3 string + arg4 string + }{arg1, arg2, arg3, arg4}) + fake.recordInvocation("GetLatestChannelEntriesThatProvide", []interface{}{arg1, arg2, arg3, arg4}) + fake.getLatestChannelEntriesThatProvideMutex.Unlock() + if fake.GetLatestChannelEntriesThatProvideStub != nil { + return fake.GetLatestChannelEntriesThatProvideStub(arg1, arg2, arg3, arg4) + } + if specificReturn { + return ret.result1, ret.result2 + } + fakeReturns := fake.getLatestChannelEntriesThatProvideReturns + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeClientInterface) GetLatestChannelEntriesThatProvideCallCount() int { + fake.getLatestChannelEntriesThatProvideMutex.RLock() + defer fake.getLatestChannelEntriesThatProvideMutex.RUnlock() + return len(fake.getLatestChannelEntriesThatProvideArgsForCall) +} + +func (fake *FakeClientInterface) GetLatestChannelEntriesThatProvideCalls(stub func(context.Context, string, string, string) (*registry.ChannelEntryIterator, error)) { + fake.getLatestChannelEntriesThatProvideMutex.Lock() + defer fake.getLatestChannelEntriesThatProvideMutex.Unlock() + fake.GetLatestChannelEntriesThatProvideStub = stub +} + +func (fake *FakeClientInterface) GetLatestChannelEntriesThatProvideArgsForCall(i int) (context.Context, string, string, string) { + fake.getLatestChannelEntriesThatProvideMutex.RLock() + defer fake.getLatestChannelEntriesThatProvideMutex.RUnlock() + argsForCall := fake.getLatestChannelEntriesThatProvideArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4 +} + +func (fake *FakeClientInterface) GetLatestChannelEntriesThatProvideReturns(result1 *registry.ChannelEntryIterator, result2 error) { + fake.getLatestChannelEntriesThatProvideMutex.Lock() + defer fake.getLatestChannelEntriesThatProvideMutex.Unlock() + fake.GetLatestChannelEntriesThatProvideStub = nil + fake.getLatestChannelEntriesThatProvideReturns = struct { + result1 *registry.ChannelEntryIterator + result2 error + }{result1, result2} +} + +func (fake *FakeClientInterface) GetLatestChannelEntriesThatProvideReturnsOnCall(i int, result1 *registry.ChannelEntryIterator, result2 error) { + fake.getLatestChannelEntriesThatProvideMutex.Lock() + defer fake.getLatestChannelEntriesThatProvideMutex.Unlock() + fake.GetLatestChannelEntriesThatProvideStub = nil + if fake.getLatestChannelEntriesThatProvideReturnsOnCall == nil { + fake.getLatestChannelEntriesThatProvideReturnsOnCall = make(map[int]struct { + result1 *registry.ChannelEntryIterator + result2 error + }) + } + fake.getLatestChannelEntriesThatProvideReturnsOnCall[i] = struct { + result1 *registry.ChannelEntryIterator + result2 error + }{result1, result2} +} + +func (fake *FakeClientInterface) GetPackage(arg1 context.Context, arg2 string) (*api.Package, error) { + fake.getPackageMutex.Lock() + ret, specificReturn := fake.getPackageReturnsOnCall[len(fake.getPackageArgsForCall)] + fake.getPackageArgsForCall = append(fake.getPackageArgsForCall, struct { + arg1 context.Context + arg2 string + }{arg1, arg2}) + fake.recordInvocation("GetPackage", []interface{}{arg1, arg2}) + fake.getPackageMutex.Unlock() + if fake.GetPackageStub != nil { + return fake.GetPackageStub(arg1, arg2) + } + if specificReturn { + return ret.result1, ret.result2 + } + fakeReturns := fake.getPackageReturns + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeClientInterface) GetPackageCallCount() int { + fake.getPackageMutex.RLock() + defer fake.getPackageMutex.RUnlock() + return len(fake.getPackageArgsForCall) +} + +func (fake *FakeClientInterface) GetPackageCalls(stub func(context.Context, string) (*api.Package, error)) { + fake.getPackageMutex.Lock() + defer fake.getPackageMutex.Unlock() + fake.GetPackageStub = stub +} + +func (fake *FakeClientInterface) GetPackageArgsForCall(i int) (context.Context, string) { + fake.getPackageMutex.RLock() + defer fake.getPackageMutex.RUnlock() + argsForCall := fake.getPackageArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *FakeClientInterface) GetPackageReturns(result1 *api.Package, result2 error) { + fake.getPackageMutex.Lock() + defer fake.getPackageMutex.Unlock() + fake.GetPackageStub = nil + fake.getPackageReturns = struct { + result1 *api.Package + result2 error + }{result1, result2} +} + +func (fake *FakeClientInterface) GetPackageReturnsOnCall(i int, result1 *api.Package, result2 error) { + fake.getPackageMutex.Lock() + defer fake.getPackageMutex.Unlock() + fake.GetPackageStub = nil + if fake.getPackageReturnsOnCall == nil { + fake.getPackageReturnsOnCall = make(map[int]struct { + result1 *api.Package + result2 error + }) + } + fake.getPackageReturnsOnCall[i] = struct { + result1 *api.Package + result2 error + }{result1, result2} +} + +func (fake *FakeClientInterface) GetReplacementBundleInPackageChannel(arg1 context.Context, arg2 string, arg3 string, arg4 string) (*api.Bundle, error) { + fake.getReplacementBundleInPackageChannelMutex.Lock() + ret, specificReturn := fake.getReplacementBundleInPackageChannelReturnsOnCall[len(fake.getReplacementBundleInPackageChannelArgsForCall)] + fake.getReplacementBundleInPackageChannelArgsForCall = append(fake.getReplacementBundleInPackageChannelArgsForCall, struct { + arg1 context.Context + arg2 string + arg3 string + arg4 string + }{arg1, arg2, arg3, arg4}) + fake.recordInvocation("GetReplacementBundleInPackageChannel", []interface{}{arg1, arg2, arg3, arg4}) + fake.getReplacementBundleInPackageChannelMutex.Unlock() + if fake.GetReplacementBundleInPackageChannelStub != nil { + return fake.GetReplacementBundleInPackageChannelStub(arg1, arg2, arg3, arg4) + } + if specificReturn { + return ret.result1, ret.result2 + } + fakeReturns := fake.getReplacementBundleInPackageChannelReturns + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeClientInterface) GetReplacementBundleInPackageChannelCallCount() int { + fake.getReplacementBundleInPackageChannelMutex.RLock() + defer fake.getReplacementBundleInPackageChannelMutex.RUnlock() + return len(fake.getReplacementBundleInPackageChannelArgsForCall) +} + +func (fake *FakeClientInterface) GetReplacementBundleInPackageChannelCalls(stub func(context.Context, string, string, string) (*api.Bundle, error)) { + fake.getReplacementBundleInPackageChannelMutex.Lock() + defer fake.getReplacementBundleInPackageChannelMutex.Unlock() + fake.GetReplacementBundleInPackageChannelStub = stub +} + +func (fake *FakeClientInterface) GetReplacementBundleInPackageChannelArgsForCall(i int) (context.Context, string, string, string) { + fake.getReplacementBundleInPackageChannelMutex.RLock() + defer fake.getReplacementBundleInPackageChannelMutex.RUnlock() + argsForCall := fake.getReplacementBundleInPackageChannelArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4 +} + +func (fake *FakeClientInterface) GetReplacementBundleInPackageChannelReturns(result1 *api.Bundle, result2 error) { + fake.getReplacementBundleInPackageChannelMutex.Lock() + defer fake.getReplacementBundleInPackageChannelMutex.Unlock() + fake.GetReplacementBundleInPackageChannelStub = nil + fake.getReplacementBundleInPackageChannelReturns = struct { + result1 *api.Bundle + result2 error + }{result1, result2} +} + +func (fake *FakeClientInterface) GetReplacementBundleInPackageChannelReturnsOnCall(i int, result1 *api.Bundle, result2 error) { + fake.getReplacementBundleInPackageChannelMutex.Lock() + defer fake.getReplacementBundleInPackageChannelMutex.Unlock() + fake.GetReplacementBundleInPackageChannelStub = nil + if fake.getReplacementBundleInPackageChannelReturnsOnCall == nil { + fake.getReplacementBundleInPackageChannelReturnsOnCall = make(map[int]struct { + result1 *api.Bundle + result2 error + }) + } + fake.getReplacementBundleInPackageChannelReturnsOnCall[i] = struct { + result1 *api.Bundle + result2 error + }{result1, result2} +} + +func (fake *FakeClientInterface) HealthCheck(arg1 context.Context, arg2 time.Duration) (bool, error) { + fake.healthCheckMutex.Lock() + ret, specificReturn := fake.healthCheckReturnsOnCall[len(fake.healthCheckArgsForCall)] + fake.healthCheckArgsForCall = append(fake.healthCheckArgsForCall, struct { + arg1 context.Context + arg2 time.Duration + }{arg1, arg2}) + fake.recordInvocation("HealthCheck", []interface{}{arg1, arg2}) + fake.healthCheckMutex.Unlock() + if fake.HealthCheckStub != nil { + return fake.HealthCheckStub(arg1, arg2) + } + if specificReturn { + return ret.result1, ret.result2 + } + fakeReturns := fake.healthCheckReturns + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeClientInterface) HealthCheckCallCount() int { + fake.healthCheckMutex.RLock() + defer fake.healthCheckMutex.RUnlock() + return len(fake.healthCheckArgsForCall) +} + +func (fake *FakeClientInterface) HealthCheckCalls(stub func(context.Context, time.Duration) (bool, error)) { + fake.healthCheckMutex.Lock() + defer fake.healthCheckMutex.Unlock() + fake.HealthCheckStub = stub +} + +func (fake *FakeClientInterface) HealthCheckArgsForCall(i int) (context.Context, time.Duration) { + fake.healthCheckMutex.RLock() + defer fake.healthCheckMutex.RUnlock() + argsForCall := fake.healthCheckArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *FakeClientInterface) HealthCheckReturns(result1 bool, result2 error) { + fake.healthCheckMutex.Lock() + defer fake.healthCheckMutex.Unlock() + fake.HealthCheckStub = nil + fake.healthCheckReturns = struct { + result1 bool + result2 error + }{result1, result2} +} + +func (fake *FakeClientInterface) HealthCheckReturnsOnCall(i int, result1 bool, result2 error) { + fake.healthCheckMutex.Lock() + defer fake.healthCheckMutex.Unlock() + fake.HealthCheckStub = nil + if fake.healthCheckReturnsOnCall == nil { + fake.healthCheckReturnsOnCall = make(map[int]struct { + result1 bool + result2 error + }) + } + fake.healthCheckReturnsOnCall[i] = struct { + result1 bool + result2 error + }{result1, result2} +} + +func (fake *FakeClientInterface) ListBundles(arg1 context.Context) (*client.BundleIterator, error) { + fake.listBundlesMutex.Lock() + ret, specificReturn := fake.listBundlesReturnsOnCall[len(fake.listBundlesArgsForCall)] + fake.listBundlesArgsForCall = append(fake.listBundlesArgsForCall, struct { + arg1 context.Context + }{arg1}) + fake.recordInvocation("ListBundles", []interface{}{arg1}) + fake.listBundlesMutex.Unlock() + if fake.ListBundlesStub != nil { + return fake.ListBundlesStub(arg1) + } + if specificReturn { + return ret.result1, ret.result2 + } + fakeReturns := fake.listBundlesReturns + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeClientInterface) ListBundlesCallCount() int { + fake.listBundlesMutex.RLock() + defer fake.listBundlesMutex.RUnlock() + return len(fake.listBundlesArgsForCall) +} + +func (fake *FakeClientInterface) ListBundlesCalls(stub func(context.Context) (*client.BundleIterator, error)) { + fake.listBundlesMutex.Lock() + defer fake.listBundlesMutex.Unlock() + fake.ListBundlesStub = stub +} + +func (fake *FakeClientInterface) ListBundlesArgsForCall(i int) context.Context { + fake.listBundlesMutex.RLock() + defer fake.listBundlesMutex.RUnlock() + argsForCall := fake.listBundlesArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakeClientInterface) ListBundlesReturns(result1 *client.BundleIterator, result2 error) { + fake.listBundlesMutex.Lock() + defer fake.listBundlesMutex.Unlock() + fake.ListBundlesStub = nil + fake.listBundlesReturns = struct { + result1 *client.BundleIterator + result2 error + }{result1, result2} +} + +func (fake *FakeClientInterface) ListBundlesReturnsOnCall(i int, result1 *client.BundleIterator, result2 error) { + fake.listBundlesMutex.Lock() + defer fake.listBundlesMutex.Unlock() + fake.ListBundlesStub = nil + if fake.listBundlesReturnsOnCall == nil { + fake.listBundlesReturnsOnCall = make(map[int]struct { + result1 *client.BundleIterator + result2 error + }) + } + fake.listBundlesReturnsOnCall[i] = struct { + result1 *client.BundleIterator + result2 error + }{result1, result2} +} + +func (fake *FakeClientInterface) Invocations() map[string][][]interface{} { + fake.invocationsMutex.RLock() + defer fake.invocationsMutex.RUnlock() + fake.closeMutex.RLock() + defer fake.closeMutex.RUnlock() + fake.findBundleThatProvidesMutex.RLock() + defer fake.findBundleThatProvidesMutex.RUnlock() + fake.getBundleMutex.RLock() + defer fake.getBundleMutex.RUnlock() + fake.getBundleInPackageChannelMutex.RLock() + defer fake.getBundleInPackageChannelMutex.RUnlock() + fake.getBundleThatProvidesMutex.RLock() + defer fake.getBundleThatProvidesMutex.RUnlock() + fake.getLatestChannelEntriesThatProvideMutex.RLock() + defer fake.getLatestChannelEntriesThatProvideMutex.RUnlock() + fake.getPackageMutex.RLock() + defer fake.getPackageMutex.RUnlock() + fake.getReplacementBundleInPackageChannelMutex.RLock() + defer fake.getReplacementBundleInPackageChannelMutex.RUnlock() + fake.healthCheckMutex.RLock() + defer fake.healthCheckMutex.RUnlock() + fake.listBundlesMutex.RLock() + defer fake.listBundlesMutex.RUnlock() + copiedInvocations := map[string][][]interface{}{} + for key, value := range fake.invocations { + copiedInvocations[key] = value + } + return copiedInvocations +} + +func (fake *FakeClientInterface) recordInvocation(key string, args []interface{}) { + fake.invocationsMutex.Lock() + defer fake.invocationsMutex.Unlock() + if fake.invocations == nil { + fake.invocations = map[string][][]interface{}{} + } + if fake.invocations[key] == nil { + fake.invocations[key] = [][]interface{}{} + } + fake.invocations[key] = append(fake.invocations[key], args) +} + +var _ registry.ClientInterface = new(FakeClientInterface) diff --git a/pkg/controller/registry/resolver/step_resolver_test.go b/pkg/controller/registry/resolver/step_resolver_test.go index e01692ce5f3..47123357a70 100644 --- a/pkg/controller/registry/resolver/step_resolver_test.go +++ b/pkg/controller/registry/resolver/step_resolver_test.go @@ -1120,7 +1120,7 @@ func bundleSteps(bundle *api.Bundle, ns, replaces string, catalog resolvercache. csv, _ := V1alpha1CSVFromBundle(bundle) replaces = csv.Spec.Replaces } - stepresources, err := NewStepResourceFromBundle(bundle, ns, replaces, catalog.Name, catalog.Namespace) + stepresources, _, err := NewStepResourceFromBundle(bundle, ns, replaces, catalog.Name, catalog.Namespace) if err != nil { panic(err) } diff --git a/pkg/controller/registry/resolver/steps.go b/pkg/controller/registry/resolver/steps.go index 7860c7108ab..229244cfe3d 100644 --- a/pkg/controller/registry/resolver/steps.go +++ b/pkg/controller/registry/resolver/steps.go @@ -6,6 +6,8 @@ import ( "fmt" "strings" + "github.com/sirupsen/logrus" + "github.com/operator-framework/operator-registry/pkg/api" extScheme "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -23,8 +25,9 @@ import ( ) const ( - secretKind = "Secret" - BundleSecretKind = "BundleSecret" + secretKind = "Secret" + BundleSecretKind = "BundleSecret" + optionalManifestsProp = "olm.manifests.optional" ) var ( @@ -112,17 +115,19 @@ func V1alpha1CSVFromBundle(bundle *api.Bundle) (*v1alpha1.ClusterServiceVersion, return csv, nil } -func NewStepResourceFromBundle(bundle *api.Bundle, namespace, replaces, catalogSourceName, catalogSourceNamespace string) ([]v1alpha1.StepResource, error) { +// NewStepResourceFromBundle returns StepResources and related Namespaces indexed in the same order. +// StepResources don't contain the resource namespace, which is required to uniquely identify a resource. +func NewStepResourceFromBundle(bundle *api.Bundle, namespace, replaces, catalogSourceName, catalogSourceNamespace string) ([]v1alpha1.StepResource, []string, error) { csv, err := V1alpha1CSVFromBundle(bundle) if err != nil { - return nil, err + return nil, nil, err } csv.SetNamespace(namespace) csv.Spec.Replaces = replaces anno, err := projection.PropertiesAnnotationFromPropertyList(bundle.Properties) if err != nil { - return nil, fmt.Errorf("failed to construct properties annotation for %q: %w", csv.GetName(), err) + return nil, nil, fmt.Errorf("failed to construct properties annotation for %q: %w", csv.GetName(), err) } annos := csv.GetAnnotations() @@ -132,40 +137,50 @@ func NewStepResourceFromBundle(bundle *api.Bundle, namespace, replaces, catalogS annos[projection.PropertiesAnnotationKey] = anno csv.SetAnnotations(annos) - step, err := NewStepResourceFromObject(csv, catalogSourceName, catalogSourceNamespace) + csvStep, err := NewStepResourceFromObject(csv, catalogSourceName, catalogSourceNamespace) if err != nil { - return nil, err + return nil, nil, err } - steps := []v1alpha1.StepResource{step} + steps := []v1alpha1.StepResource{} + namespaces := []string{} for _, object := range bundle.Object { dec := yaml.NewYAMLOrJSONDecoder(strings.NewReader(object), 10) unst := &unstructured.Unstructured{} if err := dec.Decode(unst); err != nil { - return nil, err + return nil, nil, err } - + namespaces = append(namespaces, unst.GetNamespace()) if unst.GetObjectKind().GroupVersionKind().Kind == v1alpha1.ClusterServiceVersionKind { + // Adding the CSV step only here, although it was computed earlier, to keep the namespace and step orders aligned. + steps = append(steps, csvStep) continue } step, err := NewStepResourceFromObject(unst, catalogSourceName, catalogSourceNamespace) if err != nil { - return nil, err + return nil, nil, err } steps = append(steps, step) } operatorServiceAccountSteps, err := NewServiceAccountStepResources(csv, catalogSourceName, catalogSourceNamespace) if err != nil { - return nil, err + return nil, nil, err } steps = append(steps, operatorServiceAccountSteps...) - return steps, nil + saNamespaces := []string{} + // Namespace for service accounts step is always catalogSourceNamespace + for i := 0; i < len(operatorServiceAccountSteps); i++ { + saNamespaces = append(saNamespaces, catalogSourceNamespace) + } + namespaces = append(namespaces, saNamespaces...) + + return steps, namespaces, nil } func NewStepsFromBundle(bundle *api.Bundle, namespace, replaces, catalogSourceName, catalogSourceNamespace string) ([]*v1alpha1.Step, error) { - bundleSteps, err := NewStepResourceFromBundle(bundle, namespace, replaces, catalogSourceName, catalogSourceNamespace) + bundleSteps, _, err := NewStepResourceFromBundle(bundle, namespace, replaces, catalogSourceName, catalogSourceNamespace) if err != nil { return nil, err } @@ -182,6 +197,45 @@ func NewStepsFromBundle(bundle *api.Bundle, namespace, replaces, catalogSourceNa return steps, nil } +// NewQualifiedStepsFromBundle returns the steps to be populated into the InstallPlan for a bundle +// Qualified means that steps may have been flaged as optional. +func NewQualifiedStepsFromBundle(bundle *api.Bundle, namespace, replaces, catalogSourceName, catalogSourceNamespace string, + logger *logrus.Logger) ([]*v1alpha1.Step, error) { + bundleSteps, namespaces, err := NewStepResourceFromBundle(bundle, namespace, replaces, catalogSourceName, catalogSourceNamespace) + if err != nil { + return nil, err + } + var steps []*v1alpha1.Step + isOptFunc := isOptional(bundle.Properties) + for i, s := range bundleSteps { + // Optional manifests are identified by: group/kind/namespace/name + // bundleSteps and namespaces share the same index (weak) + key := s.Group + "/" + s.Kind + "/" + namespaces[i] + "/" + s.Name + // namespace is missing + optional := isOptFunc(key) + logger.Debugf("key %s is optional: %t", key, optional) + + // CSV should be positioned first + if s.Kind == v1alpha1.ClusterServiceVersionKind { + steps = append([]*v1alpha1.Step{&v1alpha1.Step{ + Resolving: bundle.CsvName, + Resource: s, + Optional: optional, + Status: v1alpha1.StepStatusUnknown, + }}, steps...) + } else { + steps = append(steps, &v1alpha1.Step{ + Resolving: bundle.CsvName, + Resource: s, + Optional: optional, + Status: v1alpha1.StepStatusUnknown, + }) + } + } + + return steps, nil +} + // NewServiceAccountStepResources returns a list of step resources required to satisfy the RBAC requirements of the given CSV's InstallStrategy func NewServiceAccountStepResources(csv *v1alpha1.ClusterServiceVersion, catalogSourceName, catalogSourceNamespace string) ([]v1alpha1.StepResource, error) { var rbacSteps []v1alpha1.StepResource @@ -230,3 +284,39 @@ func NewServiceAccountStepResources(csv *v1alpha1.ClusterServiceVersion, catalog } return rbacSteps, nil } + +type optionalManifests struct { + Files []string `json:"manifests"` +} + +// isOptional returns a func giving whether a resource is in the list of optional manifests +// or false if any issue occurs. +func isOptional(properties []*api.Property) func(key string) bool { + if optionals, ok := getPropertyValue(properties, optionalManifestsProp); ok { + var optFiles optionalManifests + if err := json.Unmarshal([]byte(optionals), &optFiles); err == nil { + return func(key string) bool { + for _, oKey := range optFiles.Files { + if oKey == key { + return true + } + } + return false + } + } + } + return func(key string) bool { + return false + } +} + +// getPropertyValue returns the value of a specific property from an array of Property +// and true if the value is found, false otherwise +func getPropertyValue(properties []*api.Property, propertyType string) (string, bool) { + for _, property := range properties { + if property.Type == propertyType { + return property.Value, true + } + } + return "", false +} diff --git a/pkg/controller/registry/resolver/steps_test.go b/pkg/controller/registry/resolver/steps_test.go new file mode 100644 index 00000000000..7824a3f31ef --- /dev/null +++ b/pkg/controller/registry/resolver/steps_test.go @@ -0,0 +1,48 @@ +package resolver + +import ( + "fmt" + "testing" + + "github.com/operator-framework/operator-registry/pkg/api" +) + +const ( + // format is: group/kind/namespace/name + optionalManifest = "monitoring.coreos.com/PrometheusRule//myrule" + mandatoryManifest = "authorization.openshift.io/ClusterRoleBinding//mycrbinding" +) + +func TestIsOptional(t *testing.T) { + type isOptionalTests struct { + filenames []string + results []bool + } + properties := []*api.Property{ + { + Type: "olm.gvk", + Value: "{\"group\":\"kibana.k8s.elastic.co\",\"kind\":\"Kibana\",\"version\":\"v1alpha1\"}", + }, + { + Type: "olm.manifests.optional", + Value: fmt.Sprintf(`{"manifests":["%s"]}`, optionalManifest), + }, + { + Type: "olm.gvk", + Value: "{\"group\":\"apm.k8s.elastic.co\",\"kind\":\"ApmServer\",\"version\":\"v1beta1\"}", + }, + } + + ioTests := isOptionalTests{ + filenames: []string{optionalManifest, mandatoryManifest}, + results: []bool{true, false}, + } + + isOptFunc := isOptional(properties) + for i, filename := range ioTests.filenames { + result := isOptFunc(filename) + if result != ioTests.results[i] { + t.Errorf("filename: %s, got %t; want %t", filename, result, ioTests.results[i]) + } + } +} diff --git a/vendor/github.com/operator-framework/api/pkg/encoding/encoding.go b/vendor/github.com/operator-framework/api/pkg/encoding/encoding.go new file mode 100644 index 00000000000..79cee4f0c7e --- /dev/null +++ b/vendor/github.com/operator-framework/api/pkg/encoding/encoding.go @@ -0,0 +1,50 @@ +package encoding + +import ( + "bytes" + "compress/gzip" + "encoding/base64" + "io" +) + +// GzipBase64Encode applies gzip compression to the given bytes, followed by base64 encoding. +func GzipBase64Encode(data []byte) ([]byte, error) { + buf := &bytes.Buffer{} + + bWriter := base64.NewEncoder(base64.StdEncoding, buf) + zWriter := gzip.NewWriter(bWriter) + _, err := zWriter.Write(data) + if err != nil { + zWriter.Close() + bWriter.Close() + return nil, err + } + + // Ensure all gzipped bytes are flushed to the underlying base64 encoder + err = zWriter.Close() + if err != nil { + return nil, err + } + + // Ensure all base64d bytes are flushed to the underlying buffer + err = bWriter.Close() + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +// GzipBase64Decode applies base64 decoding to the given bytes, followed by gzip decompression. +func GzipBase64Decode(data []byte) ([]byte, error) { + bBuffer := bytes.NewReader(data) + + bReader := base64.NewDecoder(base64.StdEncoding, bBuffer) + zReader, err := gzip.NewReader(bReader) + if err != nil { + return nil, err + } + defer zReader.Close() + + return io.ReadAll(zReader) +} diff --git a/vendor/github.com/operator-framework/api/pkg/manifests/bundle.go b/vendor/github.com/operator-framework/api/pkg/manifests/bundle.go index e26a9b34e23..9be294a3528 100644 --- a/vendor/github.com/operator-framework/api/pkg/manifests/bundle.go +++ b/vendor/github.com/operator-framework/api/pkg/manifests/bundle.go @@ -19,6 +19,10 @@ type Bundle struct { V1beta1CRDs []*apiextensionsv1beta1.CustomResourceDefinition V1CRDs []*apiextensionsv1.CustomResourceDefinition Dependencies []*Dependency + // CompressedSize stores the gzip size of the bundle + CompressedSize int64 + // Size stores the size of the bundle + Size int64 } func (b *Bundle) ObjectsToValidate() []interface{} { diff --git a/vendor/github.com/operator-framework/api/pkg/manifests/bundleloader.go b/vendor/github.com/operator-framework/api/pkg/manifests/bundleloader.go index ec90c0cd41f..6a8d7e4cf4e 100644 --- a/vendor/github.com/operator-framework/api/pkg/manifests/bundleloader.go +++ b/vendor/github.com/operator-framework/api/pkg/manifests/bundleloader.go @@ -13,6 +13,7 @@ import ( utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/yaml" + "github.com/operator-framework/api/pkg/encoding" operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" ) @@ -35,6 +36,8 @@ func (b *bundleLoader) LoadBundle() error { errs = append(errs, err) } + errs = append(errs, b.calculateCompressedBundleSize()) + if !b.foundCSV { errs = append(errs, fmt.Errorf("unable to find a csv in bundle directory %s", b.dir)) } else if b.bundle == nil { @@ -44,6 +47,39 @@ func (b *bundleLoader) LoadBundle() error { return utilerrors.NewAggregate(errs) } +// Compress the bundle to check its size +func (b *bundleLoader) calculateCompressedBundleSize() error { + if b.bundle == nil { + return nil + } + err := filepath.Walk(b.dir, + func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + data, err := os.ReadFile(path) + if err == nil { + // Sum the bundle amount + b.bundle.Size += info.Size() + + // Sum the compressed amount + contentGzip, err := encoding.GzipBase64Encode(data) + if err != nil { + return err + } + b.bundle.CompressedSize += int64(len(contentGzip)) + } + return err + }) + if err != nil { + return err + } + return nil +} + // collectWalkErrs calls the given walk func and appends any non-nil, non skip dir error returned to the given errors slice. func collectWalkErrs(walk filepath.WalkFunc, errs *[]error) filepath.WalkFunc { return func(path string, f os.FileInfo, err error) (walkErr error) { diff --git a/vendor/github.com/operator-framework/api/pkg/validation/errors/error.go b/vendor/github.com/operator-framework/api/pkg/validation/errors/error.go index b7925e70bdd..9b4391658bf 100644 --- a/vendor/github.com/operator-framework/api/pkg/validation/errors/error.go +++ b/vendor/github.com/operator-framework/api/pkg/validation/errors/error.go @@ -117,7 +117,7 @@ func ErrInvalidBundle(detail string, value interface{}) Error { } func WarnInvalidBundle(detail string, value interface{}) Error { - return invalidBundle(LevelError, detail, value) + return invalidBundle(LevelWarn, detail, value) } func invalidBundle(lvl Level, detail string, value interface{}) Error { diff --git a/vendor/github.com/operator-framework/api/pkg/validation/internal/bundle.go b/vendor/github.com/operator-framework/api/pkg/validation/internal/bundle.go index d109deab726..9bb90a04d34 100644 --- a/vendor/github.com/operator-framework/api/pkg/validation/internal/bundle.go +++ b/vendor/github.com/operator-framework/api/pkg/validation/internal/bundle.go @@ -15,6 +15,12 @@ import ( var BundleValidator interfaces.Validator = interfaces.ValidatorFunc(validateBundles) +// max_bundle_size is the maximum size of a bundle in bytes. +// This ensures the bundle can be staged in a single ConfigMap by OLM during installation. +// The value is derived from the standard upper bound for k8s resources (~1MB). +// We will use this value to check the bundle compressed is < ~1MB +const max_bundle_size = int64(1 << (10 * 2)) + func validateBundles(objs ...interface{}) (results []errors.ManifestResult) { for _, obj := range objs { switch v := obj.(type) { @@ -32,6 +38,10 @@ func validateBundle(bundle *manifests.Bundle) (result errors.ManifestResult) { if saErrors != nil { result.Add(saErrors...) } + sizeErrors := validateBundleSize(bundle) + if sizeErrors != nil { + result.Add(sizeErrors...) + } return result } @@ -99,7 +109,7 @@ func validateOwnedCRDs(bundle *manifests.Bundle, csv *operatorsv1alpha1.ClusterS // All CRDs present in a CSV must be present in the bundle. for key := range keySet { - result.Add(errors.WarnInvalidBundle(fmt.Sprintf("CRD %q is present in bundle %q but not defined in CSV", key, bundle.Name), key)) + result.Add(errors.ErrInvalidBundle(fmt.Sprintf("CRD %q is present in bundle %q but not defined in CSV", key, bundle.Name), key)) } return result @@ -117,6 +127,62 @@ func getOwnedCustomResourceDefintionKeys(csv *operatorsv1alpha1.ClusterServiceVe return keys } +// validateBundleSize will check the bundle size according to its limits +// note that this check will raise an error if the size is bigger than the max allowed +// and warnings when: +// - we are unable to check the bundle size because we are running a check without load the bundle +// - we could identify that the bundle size is close to the limit (bigger than 85%) +func validateBundleSize(bundle *manifests.Bundle) []errors.Error { + warnPercent := 0.85 + warnSize := float64(max_bundle_size) * warnPercent + var errs []errors.Error + + if bundle.CompressedSize == 0 { + errs = append(errs, errors.WarnFailedValidation("unable to check the bundle compressed size", bundle.Name)) + return errs + } + + if bundle.Size == 0 { + errs = append(errs, errors.WarnFailedValidation("unable to check the bundle size", bundle.Name)) + return errs + } + + // From OPM (https://github.com/operator-framework/operator-registry) 1.17.5 + // and OLM (https://github.com/operator-framework/operator-lifecycle-manager) : v0.19.0 + // the total size checked is compressed + if bundle.CompressedSize > max_bundle_size { + errs = append(errs, errors.ErrInvalidBundle( + fmt.Sprintf("maximum bundle compressed size with gzip size exceeded: size=~%s , max=%s. Bundle uncompressed size is %s", + formatBytesInUnit(bundle.CompressedSize), + formatBytesInUnit(max_bundle_size), + formatBytesInUnit(bundle.Size)), + bundle.Name)) + } else if float64(bundle.CompressedSize) > warnSize { + errs = append(errs, errors.WarnInvalidBundle( + fmt.Sprintf("nearing maximum bundle compressed size with gzip: size=~%s , max=%s. Bundle uncompressed size is %s", + formatBytesInUnit(bundle.CompressedSize), + formatBytesInUnit(max_bundle_size), + formatBytesInUnit(bundle.Size)), + bundle.Name)) + } + + return errs +} + +func formatBytesInUnit(b int64) string { + const unit = 1000 + if b < unit { + return fmt.Sprintf("%d B", b) + } + div, exp := int64(unit), 0 + for n := b / unit; n >= unit; n /= unit { + div *= unit + exp++ + } + return fmt.Sprintf("%.1f %cB", + float64(b)/float64(div), "kMGTPE"[exp]) +} + // getBundleCRDKeys returns a set of definition keys for all CRDs in bundle. func getBundleCRDKeys(bundle *manifests.Bundle) (keys []schema.GroupVersionKind) { // Collect all v1 and v1beta1 CRD keys, skipping group which CSVs do not support. diff --git a/vendor/github.com/operator-framework/api/pkg/validation/internal/removed_apis.go b/vendor/github.com/operator-framework/api/pkg/validation/internal/removed_apis.go index 6cefe95b24f..44dee85d06d 100644 --- a/vendor/github.com/operator-framework/api/pkg/validation/internal/removed_apis.go +++ b/vendor/github.com/operator-framework/api/pkg/validation/internal/removed_apis.go @@ -40,6 +40,11 @@ var K8sVersionsSupportedByValidator = []string{"1.22.0", "1.25.0", "1.26.0"} // - 1.25 : https://kubernetes.io/docs/reference/using-api/deprecation-guide/#v1-25 // // - 1.26 : https://kubernetes.io/docs/reference/using-api/deprecation-guide/#v1-26 +// +// IMPORTANT: Note that in the case scenarios of 1.25 and 1.26 it is very unlikely the OperatorAuthors +// add manifests on the bundle using these APIs. On top of that some Kinds such as the CronJob +// are not currently a valid/supported by OLM and never would to be added to bundle. +// See: https://github.com/operator-framework/operator-registry/blob/v1.19.5/pkg/lib/bundle/supported_resources.go#L3-L23 var AlphaDeprecatedAPIsValidator interfaces.Validator = interfaces.ValidatorFunc(validateDeprecatedAPIsValidator) func validateDeprecatedAPIsValidator(objs ...interface{}) (results []errors.ManifestResult) { @@ -277,6 +282,11 @@ func getRemovedAPIsOn1_22From(bundle *manifests.Bundle) map[string][]string { // getRemovedAPIsOn1_25From return the list of resources which were deprecated // and are no longer be supported in 1.25. More info: https://kubernetes.io/docs/reference/using-api/deprecation-guide/#v1-25 +// +// IMPORTANT: Note that in the case scenarios of 1.25 and 1.26 it is very unlikely the OperatorAuthors +// add manifests on the bundle using these APIs. On top of that some Kinds such as the CronJob +// are not currently a valid/supported by OLM and never would to be added to bundle. +// See: https://github.com/operator-framework/operator-registry/blob/v1.19.5/pkg/lib/bundle/supported_resources.go#L3-L23 func getRemovedAPIsOn1_25From(bundle *manifests.Bundle) map[string][]string { deprecatedAPIs := make(map[string][]string) for _, obj := range bundle.Objects { @@ -315,6 +325,11 @@ func getRemovedAPIsOn1_25From(bundle *manifests.Bundle) map[string][]string { // getRemovedAPIsOn1_26From return the list of resources which were deprecated // and are no longer be supported in 1.26. More info: https://kubernetes.io/docs/reference/using-api/deprecation-guide/#v1-26 +// +// IMPORTANT: Note that in the case scenarios of 1.25 and 1.26 it is very unlikely the OperatorAuthors +// add manifests on the bundle using these APIs. On top of that some Kinds such as the CronJob +// are not currently a valid/supported by OLM and never would to be added to bundle. +// See: https://github.com/operator-framework/operator-registry/blob/v1.19.5/pkg/lib/bundle/supported_resources.go#L3-L23 func getRemovedAPIsOn1_26From(bundle *manifests.Bundle) map[string][]string { deprecatedAPIs := make(map[string][]string) for _, obj := range bundle.Objects { diff --git a/vendor/modules.txt b/vendor/modules.txt index 4d3d969ac57..8100e7c8c47 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -628,10 +628,11 @@ github.com/openshift/client-go/config/informers/externalversions/config github.com/openshift/client-go/config/informers/externalversions/config/v1 github.com/openshift/client-go/config/informers/externalversions/internalinterfaces github.com/openshift/client-go/config/listers/config/v1 -# github.com/operator-framework/api v0.11.2-0.20220128160316-8e593f1c42b9 +# github.com/operator-framework/api v0.14.0 ## explicit; go 1.17 github.com/operator-framework/api/crds github.com/operator-framework/api/pkg/constraints +github.com/operator-framework/api/pkg/encoding github.com/operator-framework/api/pkg/lib/version github.com/operator-framework/api/pkg/manifests github.com/operator-framework/api/pkg/operators