From f0a2d5cc20a49d5332ae8932ea8e44d9ea3fb338 Mon Sep 17 00:00:00 2001 From: alecmerdler Date: Fri, 26 Oct 2018 13:37:05 -0400 Subject: [PATCH] add 'spec.nativeAPIs' field to CSV and check that native APIs exist before install --- .../0000_30_02-clusterserviceversion.crd.yaml | 20 ++++++++++- .../v1alpha1/clusterserviceversion_types.go | 13 ++++++-- .../v1alpha1/zz_generated.deepcopy.go | 21 ++++++++++++ pkg/controller/operators/olm/operator_test.go | 7 +++- pkg/controller/operators/olm/requirements.go | 26 +++++++++++++-- test/e2e/csv_e2e_test.go | 33 +++++++++++++++++++ 6 files changed, 114 insertions(+), 6 deletions(-) diff --git a/deploy/chart/templates/0000_30_02-clusterserviceversion.crd.yaml b/deploy/chart/templates/0000_30_02-clusterserviceversion.crd.yaml index 48ce0417d19..830f6af827e 100644 --- a/deploy/chart/templates/0000_30_02-clusterserviceversion.crd.yaml +++ b/deploy/chart/templates/0000_30_02-clusterserviceversion.crd.yaml @@ -180,7 +180,25 @@ spec: values: type: array description: set of values for the expression - + nativeAPIs: + type: array + description: What resources are required by the Operator, but must be provided by the underlying cluster and not as an extension. + items: + type: object + required: + - group + - version + - kind + properties: + group: + type: string + description: Group of the API resource + version: + type: string + description: Version of the API resource + kind: + type: string + description: Kind of the API resource apiservicedefinitions: type: object properties: diff --git a/pkg/api/apis/operators/v1alpha1/clusterserviceversion_types.go b/pkg/api/apis/operators/v1alpha1/clusterserviceversion_types.go index 9351661b769..25f4272cb4a 100644 --- a/pkg/api/apis/operators/v1alpha1/clusterserviceversion_types.go +++ b/pkg/api/apis/operators/v1alpha1/clusterserviceversion_types.go @@ -102,14 +102,23 @@ type APIServiceDefinitions struct { Required []APIServiceDescription `json:"required,omitempty"` } -// ClusterServiceVersionSpec declarations tell the OLM how to install an operator -// that can manage apps for given version and AppType. +// NativeAPI describes the GVK of a Kubernetes resource that is required by an +// Operator, but must be provided by the underlying cluster and not an extension. +type NativeAPI struct { + Group string `json:"group"` + Version string `json:"version"` + Kind string `json:"kind"` +} + +// ClusterServiceVersionSpec declarations tell OLM how to install an operator +// that can manage apps for a given version. type ClusterServiceVersionSpec struct { InstallStrategy NamedInstallStrategy `json:"install"` Version semver.Version `json:"version,omitempty"` Maturity string `json:"maturity,omitempty"` CustomResourceDefinitions CustomResourceDefinitions `json:"customresourcedefinitions,omitempty"` APIServiceDefinitions APIServiceDefinitions `json:"apiservicedefinitions,omitempty"` + NativeAPIs []NativeAPI `json:"nativeAPIs,omitempty"` DisplayName string `json:"displayName"` Description string `json:"description,omitempty"` Keywords []string `json:"keywords,omitempty"` diff --git a/pkg/api/apis/operators/v1alpha1/zz_generated.deepcopy.go b/pkg/api/apis/operators/v1alpha1/zz_generated.deepcopy.go index a0bb0784230..048711762b2 100644 --- a/pkg/api/apis/operators/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/api/apis/operators/v1alpha1/zz_generated.deepcopy.go @@ -402,6 +402,11 @@ func (in *ClusterServiceVersionSpec) DeepCopyInto(out *ClusterServiceVersionSpec out.Version = in.Version in.CustomResourceDefinitions.DeepCopyInto(&out.CustomResourceDefinitions) in.APIServiceDefinitions.DeepCopyInto(&out.APIServiceDefinitions) + if in.NativeAPIs != nil { + in, out := &in.NativeAPIs, &out.NativeAPIs + *out = make([]NativeAPI, len(*in)) + copy(*out, *in) + } if in.Keywords != nil { in, out := &in.Keywords, &out.Keywords *out = make([]string, len(*in)) @@ -757,6 +762,22 @@ func (in *NamedInstallStrategy) DeepCopy() *NamedInstallStrategy { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NativeAPI) DeepCopyInto(out *NativeAPI) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NativeAPI. +func (in *NativeAPI) DeepCopy() *NativeAPI { + if in == nil { + return nil + } + out := new(NativeAPI) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RequirementStatus) DeepCopyInto(out *RequirementStatus) { *out = *in diff --git a/pkg/controller/operators/olm/operator_test.go b/pkg/controller/operators/olm/operator_test.go index e9507a44678..e7d7c6ffbd5 100644 --- a/pkg/controller/operators/olm/operator_test.go +++ b/pkg/controller/operators/olm/operator_test.go @@ -212,7 +212,12 @@ func installStrategy(deploymentName string, permissions []install.StrategyDeploy } } -func csv(name, namespace, replaces string, installStrategy v1alpha1.NamedInstallStrategy, owned, required []*v1beta1.CustomResourceDefinition, phase v1alpha1.ClusterServiceVersionPhase) *v1alpha1.ClusterServiceVersion { +func csv( + name, namespace, replaces string, + installStrategy v1alpha1.NamedInstallStrategy, + owned, required []*v1beta1.CustomResourceDefinition, + phase v1alpha1.ClusterServiceVersionPhase, +) *v1alpha1.ClusterServiceVersion { requiredCRDDescs := make([]v1alpha1.CRDDescription, 0) for _, crd := range required { requiredCRDDescs = append(requiredCRDDescs, v1alpha1.CRDDescription{Name: crd.GetName(), Version: crd.Spec.Versions[0].Name, Kind: crd.Spec.Names.Kind}) diff --git a/pkg/controller/operators/olm/requirements.go b/pkg/controller/operators/olm/requirements.go index 4909904205a..7c0e510a11f 100644 --- a/pkg/controller/operators/olm/requirements.go +++ b/pkg/controller/operators/olm/requirements.go @@ -12,7 +12,8 @@ import ( ) func (a *Operator) requirementStatus(strategyDetailsDeployment *install.StrategyDetailsDeployment, crdDescs []v1alpha1.CRDDescription, - ownedAPIServiceDescs []v1alpha1.APIServiceDescription, requiredAPIServiceDescs []v1alpha1.APIServiceDescription) (met bool, statuses []v1alpha1.RequirementStatus) { + ownedAPIServiceDescs []v1alpha1.APIServiceDescription, requiredAPIServiceDescs []v1alpha1.APIServiceDescription, + requiredNativeAPIs []v1alpha1.NativeAPI) (met bool, statuses []v1alpha1.RequirementStatus) { met = true // Check for CRDs @@ -125,6 +126,27 @@ func (a *Operator) requirementStatus(strategyDetailsDeployment *install.Strategy } } + for _, r := range requiredNativeAPIs { + name := fmt.Sprintf("%s.%s", r.Version, r.Group) + status := v1alpha1.RequirementStatus{ + Group: r.Group, + Version: r.Version, + Kind: r.Kind, + Name: name, + } + + if err := a.isGVKRegistered(r.Group, r.Version, r.Kind); err != nil { + status.Status = v1alpha1.RequirementStatusReasonNotPresent + met = false + statuses = append(statuses, status) + continue + } else { + status.Status = v1alpha1.RequirementStatusReasonPresent + statuses = append(statuses, status) + continue + } + } + return } @@ -238,7 +260,7 @@ func (a *Operator) requirementAndPermissionStatus(csv *v1alpha1.ClusterServiceVe return false, nil, fmt.Errorf("could not cast install strategy as type %T", strategyDetailsDeployment) } - reqMet, reqStatuses := a.requirementStatus(strategyDetailsDeployment, csv.GetAllCRDDescriptions(), csv.GetOwnedAPIServiceDescriptions(), csv.GetRequiredAPIServiceDescriptions()) + reqMet, reqStatuses := a.requirementStatus(strategyDetailsDeployment, csv.GetAllCRDDescriptions(), csv.GetOwnedAPIServiceDescriptions(), csv.GetRequiredAPIServiceDescriptions(), csv.Spec.NativeAPIs) rbacLister := a.lister.RbacV1() roleLister := rbacLister.RoleLister() diff --git a/test/e2e/csv_e2e_test.go b/test/e2e/csv_e2e_test.go index 861ac12cc54..7839ebe8287 100644 --- a/test/e2e/csv_e2e_test.go +++ b/test/e2e/csv_e2e_test.go @@ -426,6 +426,39 @@ func TestCreateCSVWithUnmetPermissionsAPIService(t *testing.T) { require.Error(t, err) } +func TestCreateCSVWithUnmetRequirementsNativeAPI(t *testing.T) { + defer cleaner.NotifyTestComplete(t, true) + + c := newKubeClient(t) + crc := newCRClient(t) + + depName := genName("dep-") + csv := v1alpha1.ClusterServiceVersion{ + TypeMeta: metav1.TypeMeta{ + Kind: v1alpha1.ClusterServiceVersionKind, + APIVersion: v1alpha1.ClusterServiceVersionAPIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: genName("csv"), + }, + Spec: v1alpha1.ClusterServiceVersionSpec{ + InstallStrategy: newNginxInstallStrategy(depName, nil, nil), + NativeAPIs: []v1alpha1.NativeAPI{{Group: "kubenative.io", Version: "v1", Kind: "Native"}}, + }, + } + + cleanupCSV, err := createCSV(t, c, crc, csv, testNamespace, false) + require.NoError(t, err) + defer cleanupCSV() + + _, err = fetchCSV(t, crc, csv.Name, csvPendingChecker) + require.NoError(t, err) + + // Shouldn't create deployment + _, err = c.GetDeployment(testNamespace, depName) + require.Error(t, err) +} + // TODO: same test but create serviceaccount instead func TestCreateCSVRequirementsMetCRD(t *testing.T) { defer cleaner.NotifyTestComplete(t, true)