diff --git a/Makefile b/Makefile index 6b01634b0a..35778abc05 100644 --- a/Makefile +++ b/Makefile @@ -179,7 +179,8 @@ release: rm -rf manifests mkdir manifests cp -R deploy/ocp/manifests/$(ver)/. manifests - find ./manifests -type f -exec sed -i '' '/^#/d' {} \; + find ./manifests -type f -exec sed -i "/^#/d" {} \; + find ./manifests -type f -exec sed -i "1{/---/d}" {} \; package: olmref=$(shell docker inspect --format='{{index .RepoDigests 0}}' quay.io/coreos/olm:$(ver)) package: diff --git a/deploy/chart/templates/0000_50_10-olm-operator.deployment.yaml b/deploy/chart/templates/0000_50_06-olm-operator.deployment.yaml similarity index 100% rename from deploy/chart/templates/0000_50_10-olm-operator.deployment.yaml rename to deploy/chart/templates/0000_50_06-olm-operator.deployment.yaml diff --git a/deploy/chart/templates/0000_50_11-catalog-operator.deployment.yaml b/deploy/chart/templates/0000_50_07-catalog-operator.deployment.yaml similarity index 100% rename from deploy/chart/templates/0000_50_11-catalog-operator.deployment.yaml rename to deploy/chart/templates/0000_50_07-catalog-operator.deployment.yaml diff --git a/deploy/chart/templates/0000_50_12-aggregated.clusterrole.yaml b/deploy/chart/templates/0000_50_08-aggregated.clusterrole.yaml similarity index 100% rename from deploy/chart/templates/0000_50_12-aggregated.clusterrole.yaml rename to deploy/chart/templates/0000_50_08-aggregated.clusterrole.yaml diff --git a/deploy/chart/templates/0000_50_13-operatorgroup.crd.yaml b/deploy/chart/templates/0000_50_09-operatorgroup.crd.yaml similarity index 86% rename from deploy/chart/templates/0000_50_13-operatorgroup.crd.yaml rename to deploy/chart/templates/0000_50_09-operatorgroup.crd.yaml index dd2a905083..18d92e2f79 100644 --- a/deploy/chart/templates/0000_50_13-operatorgroup.crd.yaml +++ b/deploy/chart/templates/0000_50_09-operatorgroup.crd.yaml @@ -29,7 +29,7 @@ spec: properties: selector: type: object - description: Label selector to find resources associated with or managed by the operator + description: Optional label selector to find resources associated with or managed by the operator properties: matchLabels: type: object @@ -61,11 +61,15 @@ spec: description: set of values for the expression targetNamespaces: type: array + description: Optional list of target namespaces. If set, OLM will ignore selector. items: type: string pattern: ^\S+$ serviceAccountName: type: string + staticProvidedAPIs: + type: boolean + description: If true, OLM will not modify the OperatorGroup's providedAPIs annotation. type: object status: properties: @@ -81,4 +85,4 @@ spec: - lastUpdated type: object required: - - metadata + - metadata \ No newline at end of file diff --git a/deploy/chart/templates/0000_50_14-olm-operators.configmap.yaml b/deploy/chart/templates/0000_50_10-olm-operators.configmap.yaml similarity index 100% rename from deploy/chart/templates/0000_50_14-olm-operators.configmap.yaml rename to deploy/chart/templates/0000_50_10-olm-operators.configmap.yaml diff --git a/deploy/chart/templates/0000_50_15-olm-operators.catalogsource.yaml b/deploy/chart/templates/0000_50_11-olm-operators.catalogsource.yaml similarity index 100% rename from deploy/chart/templates/0000_50_15-olm-operators.catalogsource.yaml rename to deploy/chart/templates/0000_50_11-olm-operators.catalogsource.yaml diff --git a/deploy/chart/templates/0000_50_16-operatorgroup-default.yaml b/deploy/chart/templates/0000_50_12-operatorgroup-default.yaml similarity index 100% rename from deploy/chart/templates/0000_50_16-operatorgroup-default.yaml rename to deploy/chart/templates/0000_50_12-operatorgroup-default.yaml diff --git a/deploy/chart/templates/0000_50_17-packageserver.subscription.yaml b/deploy/chart/templates/0000_50_13-packageserver.subscription.yaml similarity index 100% rename from deploy/chart/templates/0000_50_17-packageserver.subscription.yaml rename to deploy/chart/templates/0000_50_13-packageserver.subscription.yaml diff --git a/deploy/chart/templates/0000_50_18-operatorstatus.yaml b/deploy/chart/templates/0000_50_14-operatorstatus.yaml similarity index 100% rename from deploy/chart/templates/0000_50_18-operatorstatus.yaml rename to deploy/chart/templates/0000_50_14-operatorstatus.yaml diff --git a/deploy/ocp/manifests/0.8.1/0000_50_00-namespace.yaml b/deploy/ocp/manifests/0.8.1/0000_50_00-namespace.yaml index 3826583ee0..688e633455 100644 --- a/deploy/ocp/manifests/0.8.1/0000_50_00-namespace.yaml +++ b/deploy/ocp/manifests/0.8.1/0000_50_00-namespace.yaml @@ -1,4 +1,4 @@ -##--- +--- # Source: olm/templates/0000_50_00-namespace.yaml apiVersion: v1 kind: Namespace diff --git a/deploy/ocp/manifests/0.8.1/0000_50_01-olm-operator.serviceaccount.yaml b/deploy/ocp/manifests/0.8.1/0000_50_01-olm-operator.serviceaccount.yaml index 0aeece80a7..d997fe98aa 100644 --- a/deploy/ocp/manifests/0.8.1/0000_50_01-olm-operator.serviceaccount.yaml +++ b/deploy/ocp/manifests/0.8.1/0000_50_01-olm-operator.serviceaccount.yaml @@ -1,4 +1,4 @@ -##--- +--- # Source: olm/templates/0000_50_01-olm-operator.serviceaccount.yaml apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole diff --git a/deploy/ocp/manifests/0.8.1/0000_50_02-clusterserviceversion.crd.yaml b/deploy/ocp/manifests/0.8.1/0000_50_02-clusterserviceversion.crd.yaml index f7d8e6bf54..e9a53cd07f 100644 --- a/deploy/ocp/manifests/0.8.1/0000_50_02-clusterserviceversion.crd.yaml +++ b/deploy/ocp/manifests/0.8.1/0000_50_02-clusterserviceversion.crd.yaml @@ -1,4 +1,4 @@ -##--- +--- # Source: olm/templates/0000_50_02-clusterserviceversion.crd.yaml apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition diff --git a/deploy/ocp/manifests/0.8.1/0000_50_03-installplan.crd.yaml b/deploy/ocp/manifests/0.8.1/0000_50_03-installplan.crd.yaml index 9c48076164..e4df8d6077 100644 --- a/deploy/ocp/manifests/0.8.1/0000_50_03-installplan.crd.yaml +++ b/deploy/ocp/manifests/0.8.1/0000_50_03-installplan.crd.yaml @@ -1,4 +1,4 @@ -##--- +--- # Source: olm/templates/0000_50_03-installplan.crd.yaml apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition diff --git a/deploy/ocp/manifests/0.8.1/0000_50_04-subscription.crd.yaml b/deploy/ocp/manifests/0.8.1/0000_50_04-subscription.crd.yaml index 5f93f34ace..1dc36f7b0a 100644 --- a/deploy/ocp/manifests/0.8.1/0000_50_04-subscription.crd.yaml +++ b/deploy/ocp/manifests/0.8.1/0000_50_04-subscription.crd.yaml @@ -1,4 +1,4 @@ -##--- +--- # Source: olm/templates/0000_50_04-subscription.crd.yaml apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition diff --git a/deploy/ocp/manifests/0.8.1/0000_50_05-catalogsource.crd.yaml b/deploy/ocp/manifests/0.8.1/0000_50_05-catalogsource.crd.yaml index ddb2354515..e6d5cb5466 100644 --- a/deploy/ocp/manifests/0.8.1/0000_50_05-catalogsource.crd.yaml +++ b/deploy/ocp/manifests/0.8.1/0000_50_05-catalogsource.crd.yaml @@ -1,4 +1,4 @@ -##--- +--- # Source: olm/templates/0000_50_05-catalogsource.crd.yaml apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition diff --git a/deploy/ocp/manifests/0.8.1/0000_50_10-olm-operator.deployment.yaml b/deploy/ocp/manifests/0.8.1/0000_50_06-olm-operator.deployment.yaml similarity index 95% rename from deploy/ocp/manifests/0.8.1/0000_50_10-olm-operator.deployment.yaml rename to deploy/ocp/manifests/0.8.1/0000_50_06-olm-operator.deployment.yaml index 96f3ab3261..06060c226e 100644 --- a/deploy/ocp/manifests/0.8.1/0000_50_10-olm-operator.deployment.yaml +++ b/deploy/ocp/manifests/0.8.1/0000_50_06-olm-operator.deployment.yaml @@ -1,5 +1,5 @@ -##--- -# Source: olm/templates/0000_50_10-olm-operator.deployment.yaml +--- +# Source: olm/templates/0000_50_06-olm-operator.deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: diff --git a/deploy/ocp/manifests/0.8.1/0000_50_11-catalog-operator.deployment.yaml b/deploy/ocp/manifests/0.8.1/0000_50_07-catalog-operator.deployment.yaml similarity index 94% rename from deploy/ocp/manifests/0.8.1/0000_50_11-catalog-operator.deployment.yaml rename to deploy/ocp/manifests/0.8.1/0000_50_07-catalog-operator.deployment.yaml index 4c49496392..456f075a22 100644 --- a/deploy/ocp/manifests/0.8.1/0000_50_11-catalog-operator.deployment.yaml +++ b/deploy/ocp/manifests/0.8.1/0000_50_07-catalog-operator.deployment.yaml @@ -1,5 +1,5 @@ -##--- -# Source: olm/templates/0000_50_11-catalog-operator.deployment.yaml +--- +# Source: olm/templates/0000_50_07-catalog-operator.deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: diff --git a/deploy/upstream/manifests/0.8.1/0000_50_12-aggregated.clusterrole.yaml b/deploy/ocp/manifests/0.8.1/0000_50_08-aggregated.clusterrole.yaml similarity index 92% rename from deploy/upstream/manifests/0.8.1/0000_50_12-aggregated.clusterrole.yaml rename to deploy/ocp/manifests/0.8.1/0000_50_08-aggregated.clusterrole.yaml index adfebe48e6..6f333a197e 100644 --- a/deploy/upstream/manifests/0.8.1/0000_50_12-aggregated.clusterrole.yaml +++ b/deploy/ocp/manifests/0.8.1/0000_50_08-aggregated.clusterrole.yaml @@ -1,5 +1,5 @@ -##--- -# Source: olm/templates/0000_50_12-aggregated.clusterrole.yaml +--- +# Source: olm/templates/0000_50_08-aggregated.clusterrole.yaml kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: diff --git a/deploy/ocp/manifests/0.8.1/0000_50_13-operatorgroup.crd.yaml b/deploy/ocp/manifests/0.8.1/0000_50_09-operatorgroup.crd.yaml similarity index 84% rename from deploy/ocp/manifests/0.8.1/0000_50_13-operatorgroup.crd.yaml rename to deploy/ocp/manifests/0.8.1/0000_50_09-operatorgroup.crd.yaml index 8faa1bb107..43f4c08c23 100644 --- a/deploy/ocp/manifests/0.8.1/0000_50_13-operatorgroup.crd.yaml +++ b/deploy/ocp/manifests/0.8.1/0000_50_09-operatorgroup.crd.yaml @@ -1,5 +1,5 @@ -##--- -# Source: olm/templates/0000_50_13-operatorgroup.crd.yaml +--- +# Source: olm/templates/0000_50_09-operatorgroup.crd.yaml apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: @@ -31,7 +31,7 @@ spec: properties: selector: type: object - description: Label selector to find resources associated with or managed by the operator + description: Optional label selector to find resources associated with or managed by the operator properties: matchLabels: type: object @@ -63,11 +63,15 @@ spec: description: set of values for the expression targetNamespaces: type: array + description: Optional list of target namespaces. If set, OLM will ignore selector. items: type: string pattern: ^\S+$ serviceAccountName: type: string + staticProvidedAPIs: + type: boolean + description: If true, OLM will not modify the OperatorGroup's providedAPIs annotation. type: object status: properties: @@ -83,4 +87,4 @@ spec: - lastUpdated type: object required: - - metadata + - metadata \ No newline at end of file diff --git a/deploy/ocp/manifests/0.8.1/0000_50_14-olm-operators.configmap.yaml b/deploy/ocp/manifests/0.8.1/0000_50_10-olm-operators.configmap.yaml similarity index 98% rename from deploy/ocp/manifests/0.8.1/0000_50_14-olm-operators.configmap.yaml rename to deploy/ocp/manifests/0.8.1/0000_50_10-olm-operators.configmap.yaml index 558be678af..5dc4696ca1 100644 --- a/deploy/ocp/manifests/0.8.1/0000_50_14-olm-operators.configmap.yaml +++ b/deploy/ocp/manifests/0.8.1/0000_50_10-olm-operators.configmap.yaml @@ -1,5 +1,5 @@ -##--- -# Source: olm/templates/0000_50_14-olm-operators.configmap.yaml +--- +# Source: olm/templates/0000_50_10-olm-operators.configmap.yaml kind: ConfigMap apiVersion: v1 metadata: diff --git a/deploy/ocp/manifests/0.8.1/0000_50_15-olm-operators.catalogsource.yaml b/deploy/ocp/manifests/0.8.1/0000_50_11-olm-operators.catalogsource.yaml similarity index 83% rename from deploy/ocp/manifests/0.8.1/0000_50_15-olm-operators.catalogsource.yaml rename to deploy/ocp/manifests/0.8.1/0000_50_11-olm-operators.catalogsource.yaml index 7e58c26b13..8f508885c4 100644 --- a/deploy/ocp/manifests/0.8.1/0000_50_15-olm-operators.catalogsource.yaml +++ b/deploy/ocp/manifests/0.8.1/0000_50_11-olm-operators.catalogsource.yaml @@ -1,5 +1,5 @@ -##--- -# Source: olm/templates/0000_50_15-olm-operators.catalogsource.yaml +--- +# Source: olm/templates/0000_50_11-olm-operators.catalogsource.yaml #! validate-crd: ./deploy/chart/templates/05-catalogsource.crd.yaml #! parse-kind: CatalogSource apiVersion: operators.coreos.com/v1alpha1 diff --git a/deploy/ocp/manifests/0.8.1/0000_50_16-operatorgroup-default.yaml b/deploy/ocp/manifests/0.8.1/0000_50_12-operatorgroup-default.yaml similarity index 83% rename from deploy/ocp/manifests/0.8.1/0000_50_16-operatorgroup-default.yaml rename to deploy/ocp/manifests/0.8.1/0000_50_12-operatorgroup-default.yaml index 6a03f0832e..3ae2d44ff1 100644 --- a/deploy/ocp/manifests/0.8.1/0000_50_16-operatorgroup-default.yaml +++ b/deploy/ocp/manifests/0.8.1/0000_50_12-operatorgroup-default.yaml @@ -1,5 +1,5 @@ -##--- -# Source: olm/templates/0000_50_16-operatorgroup-default.yaml +--- +# Source: olm/templates/0000_50_12-operatorgroup-default.yaml apiVersion: operators.coreos.com/v1alpha2 kind: OperatorGroup metadata: diff --git a/deploy/okd/manifests/0.8.1/0000_50_17-packageserver.subscription.yaml b/deploy/ocp/manifests/0.8.1/0000_50_13-packageserver.subscription.yaml similarity index 83% rename from deploy/okd/manifests/0.8.1/0000_50_17-packageserver.subscription.yaml rename to deploy/ocp/manifests/0.8.1/0000_50_13-packageserver.subscription.yaml index 56ec0dee17..3210135b2a 100644 --- a/deploy/okd/manifests/0.8.1/0000_50_17-packageserver.subscription.yaml +++ b/deploy/ocp/manifests/0.8.1/0000_50_13-packageserver.subscription.yaml @@ -1,5 +1,5 @@ -##--- -# Source: olm/templates/0000_50_17-packageserver.subscription.yaml +--- +# Source: olm/templates/0000_50_13-packageserver.subscription.yaml #! validate-crd: ./deploy/chart/templates/04-subscription.crd.yaml #! parse-kind: Subscription apiVersion: operators.coreos.com/v1alpha1 diff --git a/deploy/ocp/manifests/0.8.1/0000_50_18-operatorstatus.yaml b/deploy/ocp/manifests/0.8.1/0000_50_14-operatorstatus.yaml similarity index 62% rename from deploy/ocp/manifests/0.8.1/0000_50_18-operatorstatus.yaml rename to deploy/ocp/manifests/0.8.1/0000_50_14-operatorstatus.yaml index 84b151eb25..0167a6c5df 100644 --- a/deploy/ocp/manifests/0.8.1/0000_50_18-operatorstatus.yaml +++ b/deploy/ocp/manifests/0.8.1/0000_50_14-operatorstatus.yaml @@ -1,5 +1,5 @@ -##--- -# Source: olm/templates/0000_50_18-operatorstatus.yaml +--- +# Source: olm/templates/0000_50_14-operatorstatus.yaml apiVersion: config.openshift.io/v1 kind: ClusterOperator diff --git a/deploy/ocp/manifests/0.8.1/image-references b/deploy/ocp/manifests/0.8.1/image-references index c0a35b4915..4c127addf4 100644 --- a/deploy/ocp/manifests/0.8.1/image-references +++ b/deploy/ocp/manifests/0.8.1/image-references @@ -1,4 +1,4 @@ -##--- +--- # Source: olm/templates/image-references kind: ImageStream diff --git a/deploy/ocp/values.yaml b/deploy/ocp/values.yaml index d124310b82..015b77cbf0 100644 --- a/deploy/ocp/values.yaml +++ b/deploy/ocp/values.yaml @@ -39,4 +39,4 @@ package: beta.kubernetes.io/os: linux node-role.kubernetes.io/master: "" tolerations: - - operator: Exists \ No newline at end of file + - operator: Exists diff --git a/deploy/okd/manifests/0.8.1/0000_50_00-namespace.yaml b/deploy/okd/manifests/0.8.1/0000_50_00-namespace.yaml index 3826583ee0..688e633455 100644 --- a/deploy/okd/manifests/0.8.1/0000_50_00-namespace.yaml +++ b/deploy/okd/manifests/0.8.1/0000_50_00-namespace.yaml @@ -1,4 +1,4 @@ -##--- +--- # Source: olm/templates/0000_50_00-namespace.yaml apiVersion: v1 kind: Namespace diff --git a/deploy/okd/manifests/0.8.1/0000_50_01-olm-operator.serviceaccount.yaml b/deploy/okd/manifests/0.8.1/0000_50_01-olm-operator.serviceaccount.yaml index 0aeece80a7..d997fe98aa 100644 --- a/deploy/okd/manifests/0.8.1/0000_50_01-olm-operator.serviceaccount.yaml +++ b/deploy/okd/manifests/0.8.1/0000_50_01-olm-operator.serviceaccount.yaml @@ -1,4 +1,4 @@ -##--- +--- # Source: olm/templates/0000_50_01-olm-operator.serviceaccount.yaml apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole diff --git a/deploy/okd/manifests/0.8.1/0000_50_02-clusterserviceversion.crd.yaml b/deploy/okd/manifests/0.8.1/0000_50_02-clusterserviceversion.crd.yaml index f7d8e6bf54..e9a53cd07f 100644 --- a/deploy/okd/manifests/0.8.1/0000_50_02-clusterserviceversion.crd.yaml +++ b/deploy/okd/manifests/0.8.1/0000_50_02-clusterserviceversion.crd.yaml @@ -1,4 +1,4 @@ -##--- +--- # Source: olm/templates/0000_50_02-clusterserviceversion.crd.yaml apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition diff --git a/deploy/okd/manifests/0.8.1/0000_50_03-installplan.crd.yaml b/deploy/okd/manifests/0.8.1/0000_50_03-installplan.crd.yaml index 9c48076164..e4df8d6077 100644 --- a/deploy/okd/manifests/0.8.1/0000_50_03-installplan.crd.yaml +++ b/deploy/okd/manifests/0.8.1/0000_50_03-installplan.crd.yaml @@ -1,4 +1,4 @@ -##--- +--- # Source: olm/templates/0000_50_03-installplan.crd.yaml apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition diff --git a/deploy/okd/manifests/0.8.1/0000_50_04-subscription.crd.yaml b/deploy/okd/manifests/0.8.1/0000_50_04-subscription.crd.yaml index 5f93f34ace..1dc36f7b0a 100644 --- a/deploy/okd/manifests/0.8.1/0000_50_04-subscription.crd.yaml +++ b/deploy/okd/manifests/0.8.1/0000_50_04-subscription.crd.yaml @@ -1,4 +1,4 @@ -##--- +--- # Source: olm/templates/0000_50_04-subscription.crd.yaml apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition diff --git a/deploy/okd/manifests/0.8.1/0000_50_05-catalogsource.crd.yaml b/deploy/okd/manifests/0.8.1/0000_50_05-catalogsource.crd.yaml index ddb2354515..e6d5cb5466 100644 --- a/deploy/okd/manifests/0.8.1/0000_50_05-catalogsource.crd.yaml +++ b/deploy/okd/manifests/0.8.1/0000_50_05-catalogsource.crd.yaml @@ -1,4 +1,4 @@ -##--- +--- # Source: olm/templates/0000_50_05-catalogsource.crd.yaml apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition diff --git a/deploy/okd/manifests/0.8.1/0000_50_10-olm-operator.deployment.yaml b/deploy/okd/manifests/0.8.1/0000_50_06-olm-operator.deployment.yaml similarity index 94% rename from deploy/okd/manifests/0.8.1/0000_50_10-olm-operator.deployment.yaml rename to deploy/okd/manifests/0.8.1/0000_50_06-olm-operator.deployment.yaml index 86c29d76ce..e18011f11c 100644 --- a/deploy/okd/manifests/0.8.1/0000_50_10-olm-operator.deployment.yaml +++ b/deploy/okd/manifests/0.8.1/0000_50_06-olm-operator.deployment.yaml @@ -1,5 +1,5 @@ -##--- -# Source: olm/templates/0000_50_10-olm-operator.deployment.yaml +--- +# Source: olm/templates/0000_50_06-olm-operator.deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: diff --git a/deploy/okd/manifests/0.8.1/0000_50_11-catalog-operator.deployment.yaml b/deploy/okd/manifests/0.8.1/0000_50_07-catalog-operator.deployment.yaml similarity index 94% rename from deploy/okd/manifests/0.8.1/0000_50_11-catalog-operator.deployment.yaml rename to deploy/okd/manifests/0.8.1/0000_50_07-catalog-operator.deployment.yaml index 1ce1c3cf24..9f3e18bbee 100644 --- a/deploy/okd/manifests/0.8.1/0000_50_11-catalog-operator.deployment.yaml +++ b/deploy/okd/manifests/0.8.1/0000_50_07-catalog-operator.deployment.yaml @@ -1,5 +1,5 @@ -##--- -# Source: olm/templates/0000_50_11-catalog-operator.deployment.yaml +--- +# Source: olm/templates/0000_50_07-catalog-operator.deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: diff --git a/deploy/okd/manifests/0.8.1/0000_50_12-aggregated.clusterrole.yaml b/deploy/okd/manifests/0.8.1/0000_50_08-aggregated.clusterrole.yaml similarity index 92% rename from deploy/okd/manifests/0.8.1/0000_50_12-aggregated.clusterrole.yaml rename to deploy/okd/manifests/0.8.1/0000_50_08-aggregated.clusterrole.yaml index adfebe48e6..6f333a197e 100644 --- a/deploy/okd/manifests/0.8.1/0000_50_12-aggregated.clusterrole.yaml +++ b/deploy/okd/manifests/0.8.1/0000_50_08-aggregated.clusterrole.yaml @@ -1,5 +1,5 @@ -##--- -# Source: olm/templates/0000_50_12-aggregated.clusterrole.yaml +--- +# Source: olm/templates/0000_50_08-aggregated.clusterrole.yaml kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: diff --git a/deploy/okd/manifests/0.8.1/0000_50_13-operatorgroup.crd.yaml b/deploy/okd/manifests/0.8.1/0000_50_09-operatorgroup.crd.yaml similarity index 84% rename from deploy/okd/manifests/0.8.1/0000_50_13-operatorgroup.crd.yaml rename to deploy/okd/manifests/0.8.1/0000_50_09-operatorgroup.crd.yaml index 8faa1bb107..43f4c08c23 100644 --- a/deploy/okd/manifests/0.8.1/0000_50_13-operatorgroup.crd.yaml +++ b/deploy/okd/manifests/0.8.1/0000_50_09-operatorgroup.crd.yaml @@ -1,5 +1,5 @@ -##--- -# Source: olm/templates/0000_50_13-operatorgroup.crd.yaml +--- +# Source: olm/templates/0000_50_09-operatorgroup.crd.yaml apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: @@ -31,7 +31,7 @@ spec: properties: selector: type: object - description: Label selector to find resources associated with or managed by the operator + description: Optional label selector to find resources associated with or managed by the operator properties: matchLabels: type: object @@ -63,11 +63,15 @@ spec: description: set of values for the expression targetNamespaces: type: array + description: Optional list of target namespaces. If set, OLM will ignore selector. items: type: string pattern: ^\S+$ serviceAccountName: type: string + staticProvidedAPIs: + type: boolean + description: If true, OLM will not modify the OperatorGroup's providedAPIs annotation. type: object status: properties: @@ -83,4 +87,4 @@ spec: - lastUpdated type: object required: - - metadata + - metadata \ No newline at end of file diff --git a/deploy/okd/manifests/0.8.1/0000_50_14-olm-operators.configmap.yaml b/deploy/okd/manifests/0.8.1/0000_50_10-olm-operators.configmap.yaml similarity index 98% rename from deploy/okd/manifests/0.8.1/0000_50_14-olm-operators.configmap.yaml rename to deploy/okd/manifests/0.8.1/0000_50_10-olm-operators.configmap.yaml index 318e81a34f..0e885a6417 100644 --- a/deploy/okd/manifests/0.8.1/0000_50_14-olm-operators.configmap.yaml +++ b/deploy/okd/manifests/0.8.1/0000_50_10-olm-operators.configmap.yaml @@ -1,5 +1,5 @@ -##--- -# Source: olm/templates/0000_50_14-olm-operators.configmap.yaml +--- +# Source: olm/templates/0000_50_10-olm-operators.configmap.yaml kind: ConfigMap apiVersion: v1 metadata: diff --git a/deploy/okd/manifests/0.8.1/0000_50_15-olm-operators.catalogsource.yaml b/deploy/okd/manifests/0.8.1/0000_50_11-olm-operators.catalogsource.yaml similarity index 83% rename from deploy/okd/manifests/0.8.1/0000_50_15-olm-operators.catalogsource.yaml rename to deploy/okd/manifests/0.8.1/0000_50_11-olm-operators.catalogsource.yaml index 7e58c26b13..8f508885c4 100644 --- a/deploy/okd/manifests/0.8.1/0000_50_15-olm-operators.catalogsource.yaml +++ b/deploy/okd/manifests/0.8.1/0000_50_11-olm-operators.catalogsource.yaml @@ -1,5 +1,5 @@ -##--- -# Source: olm/templates/0000_50_15-olm-operators.catalogsource.yaml +--- +# Source: olm/templates/0000_50_11-olm-operators.catalogsource.yaml #! validate-crd: ./deploy/chart/templates/05-catalogsource.crd.yaml #! parse-kind: CatalogSource apiVersion: operators.coreos.com/v1alpha1 diff --git a/deploy/okd/manifests/0.8.1/0000_50_16-operatorgroup-default.yaml b/deploy/okd/manifests/0.8.1/0000_50_12-operatorgroup-default.yaml similarity index 83% rename from deploy/okd/manifests/0.8.1/0000_50_16-operatorgroup-default.yaml rename to deploy/okd/manifests/0.8.1/0000_50_12-operatorgroup-default.yaml index 6a03f0832e..3ae2d44ff1 100644 --- a/deploy/okd/manifests/0.8.1/0000_50_16-operatorgroup-default.yaml +++ b/deploy/okd/manifests/0.8.1/0000_50_12-operatorgroup-default.yaml @@ -1,5 +1,5 @@ -##--- -# Source: olm/templates/0000_50_16-operatorgroup-default.yaml +--- +# Source: olm/templates/0000_50_12-operatorgroup-default.yaml apiVersion: operators.coreos.com/v1alpha2 kind: OperatorGroup metadata: diff --git a/deploy/ocp/manifests/0.8.1/0000_50_17-packageserver.subscription.yaml b/deploy/okd/manifests/0.8.1/0000_50_13-packageserver.subscription.yaml similarity index 83% rename from deploy/ocp/manifests/0.8.1/0000_50_17-packageserver.subscription.yaml rename to deploy/okd/manifests/0.8.1/0000_50_13-packageserver.subscription.yaml index 56ec0dee17..3210135b2a 100644 --- a/deploy/ocp/manifests/0.8.1/0000_50_17-packageserver.subscription.yaml +++ b/deploy/okd/manifests/0.8.1/0000_50_13-packageserver.subscription.yaml @@ -1,5 +1,5 @@ -##--- -# Source: olm/templates/0000_50_17-packageserver.subscription.yaml +--- +# Source: olm/templates/0000_50_13-packageserver.subscription.yaml #! validate-crd: ./deploy/chart/templates/04-subscription.crd.yaml #! parse-kind: Subscription apiVersion: operators.coreos.com/v1alpha1 diff --git a/deploy/okd/manifests/0.8.1/0000_50_18-operatorstatus.yaml b/deploy/okd/manifests/0.8.1/0000_50_18-operatorstatus.yaml deleted file mode 100644 index 668d682b01..0000000000 --- a/deploy/okd/manifests/0.8.1/0000_50_18-operatorstatus.yaml +++ /dev/null @@ -1,6 +0,0 @@ -##--- -# Source: olm/templates/0000_50_18-operatorstatus.yaml -apiVersion: config.openshift.io/v1 -kind: ClusterOperator -metadata: - name: operator-lifecycle-manager diff --git a/deploy/okd/values.yaml b/deploy/okd/values.yaml index 6164d4bb9a..7af53a1a8b 100644 --- a/deploy/okd/values.yaml +++ b/deploy/okd/values.yaml @@ -24,4 +24,4 @@ package: ref: quay.io/coreos/olm@sha256:995a181839f301585a0e115c083619b6d73812c58a8444d7b13b8e407010325f pullPolicy: Always service: - internalPort: 5443 \ No newline at end of file + internalPort: 5443 diff --git a/deploy/upstream/manifests/0.8.1/0000_50_00-namespace.yaml b/deploy/upstream/manifests/0.8.1/0000_50_00-namespace.yaml index 4cdb70541c..4e5cdce559 100644 --- a/deploy/upstream/manifests/0.8.1/0000_50_00-namespace.yaml +++ b/deploy/upstream/manifests/0.8.1/0000_50_00-namespace.yaml @@ -1,4 +1,4 @@ -##--- +--- # Source: olm/templates/0000_50_00-namespace.yaml apiVersion: v1 kind: Namespace diff --git a/deploy/upstream/manifests/0.8.1/0000_50_01-olm-operator.serviceaccount.yaml b/deploy/upstream/manifests/0.8.1/0000_50_01-olm-operator.serviceaccount.yaml index 7ab34cb11e..38d60253f6 100644 --- a/deploy/upstream/manifests/0.8.1/0000_50_01-olm-operator.serviceaccount.yaml +++ b/deploy/upstream/manifests/0.8.1/0000_50_01-olm-operator.serviceaccount.yaml @@ -1,4 +1,4 @@ -##--- +--- # Source: olm/templates/0000_50_01-olm-operator.serviceaccount.yaml apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole diff --git a/deploy/upstream/manifests/0.8.1/0000_50_02-clusterserviceversion.crd.yaml b/deploy/upstream/manifests/0.8.1/0000_50_02-clusterserviceversion.crd.yaml index f7d8e6bf54..e9a53cd07f 100644 --- a/deploy/upstream/manifests/0.8.1/0000_50_02-clusterserviceversion.crd.yaml +++ b/deploy/upstream/manifests/0.8.1/0000_50_02-clusterserviceversion.crd.yaml @@ -1,4 +1,4 @@ -##--- +--- # Source: olm/templates/0000_50_02-clusterserviceversion.crd.yaml apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition diff --git a/deploy/upstream/manifests/0.8.1/0000_50_03-installplan.crd.yaml b/deploy/upstream/manifests/0.8.1/0000_50_03-installplan.crd.yaml index 9c48076164..e4df8d6077 100644 --- a/deploy/upstream/manifests/0.8.1/0000_50_03-installplan.crd.yaml +++ b/deploy/upstream/manifests/0.8.1/0000_50_03-installplan.crd.yaml @@ -1,4 +1,4 @@ -##--- +--- # Source: olm/templates/0000_50_03-installplan.crd.yaml apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition diff --git a/deploy/upstream/manifests/0.8.1/0000_50_04-subscription.crd.yaml b/deploy/upstream/manifests/0.8.1/0000_50_04-subscription.crd.yaml index 5f93f34ace..1dc36f7b0a 100644 --- a/deploy/upstream/manifests/0.8.1/0000_50_04-subscription.crd.yaml +++ b/deploy/upstream/manifests/0.8.1/0000_50_04-subscription.crd.yaml @@ -1,4 +1,4 @@ -##--- +--- # Source: olm/templates/0000_50_04-subscription.crd.yaml apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition diff --git a/deploy/upstream/manifests/0.8.1/0000_50_05-catalogsource.crd.yaml b/deploy/upstream/manifests/0.8.1/0000_50_05-catalogsource.crd.yaml index ddb2354515..e6d5cb5466 100644 --- a/deploy/upstream/manifests/0.8.1/0000_50_05-catalogsource.crd.yaml +++ b/deploy/upstream/manifests/0.8.1/0000_50_05-catalogsource.crd.yaml @@ -1,4 +1,4 @@ -##--- +--- # Source: olm/templates/0000_50_05-catalogsource.crd.yaml apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition diff --git a/deploy/upstream/manifests/0.8.1/0000_50_10-olm-operator.deployment.yaml b/deploy/upstream/manifests/0.8.1/0000_50_06-olm-operator.deployment.yaml similarity index 94% rename from deploy/upstream/manifests/0.8.1/0000_50_10-olm-operator.deployment.yaml rename to deploy/upstream/manifests/0.8.1/0000_50_06-olm-operator.deployment.yaml index 33b401b40f..07b760eaa1 100644 --- a/deploy/upstream/manifests/0.8.1/0000_50_10-olm-operator.deployment.yaml +++ b/deploy/upstream/manifests/0.8.1/0000_50_06-olm-operator.deployment.yaml @@ -1,5 +1,5 @@ -##--- -# Source: olm/templates/0000_50_10-olm-operator.deployment.yaml +--- +# Source: olm/templates/0000_50_06-olm-operator.deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: diff --git a/deploy/upstream/manifests/0.8.1/0000_50_11-catalog-operator.deployment.yaml b/deploy/upstream/manifests/0.8.1/0000_50_07-catalog-operator.deployment.yaml similarity index 93% rename from deploy/upstream/manifests/0.8.1/0000_50_11-catalog-operator.deployment.yaml rename to deploy/upstream/manifests/0.8.1/0000_50_07-catalog-operator.deployment.yaml index 710eba2016..6b3db4f62c 100644 --- a/deploy/upstream/manifests/0.8.1/0000_50_11-catalog-operator.deployment.yaml +++ b/deploy/upstream/manifests/0.8.1/0000_50_07-catalog-operator.deployment.yaml @@ -1,5 +1,5 @@ -##--- -# Source: olm/templates/0000_50_11-catalog-operator.deployment.yaml +--- +# Source: olm/templates/0000_50_07-catalog-operator.deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: diff --git a/deploy/ocp/manifests/0.8.1/0000_50_12-aggregated.clusterrole.yaml b/deploy/upstream/manifests/0.8.1/0000_50_08-aggregated.clusterrole.yaml similarity index 92% rename from deploy/ocp/manifests/0.8.1/0000_50_12-aggregated.clusterrole.yaml rename to deploy/upstream/manifests/0.8.1/0000_50_08-aggregated.clusterrole.yaml index adfebe48e6..6f333a197e 100644 --- a/deploy/ocp/manifests/0.8.1/0000_50_12-aggregated.clusterrole.yaml +++ b/deploy/upstream/manifests/0.8.1/0000_50_08-aggregated.clusterrole.yaml @@ -1,5 +1,5 @@ -##--- -# Source: olm/templates/0000_50_12-aggregated.clusterrole.yaml +--- +# Source: olm/templates/0000_50_08-aggregated.clusterrole.yaml kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: diff --git a/deploy/upstream/manifests/0.8.1/0000_50_09-operatorgroup.crd.yaml b/deploy/upstream/manifests/0.8.1/0000_50_09-operatorgroup.crd.yaml new file mode 100644 index 0000000000..43f4c08c23 --- /dev/null +++ b/deploy/upstream/manifests/0.8.1/0000_50_09-operatorgroup.crd.yaml @@ -0,0 +1,90 @@ +--- +# Source: olm/templates/0000_50_09-operatorgroup.crd.yaml +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: operatorgroups.operators.coreos.com +spec: + group: operators.coreos.com + version: v1alpha2 + versions: + - name: v1alpha2 + served: true + storage: true + names: + plural: operatorgroups + singular: operatorgroup + kind: OperatorGroup + listKind: OperatorGroupList + shortNames: + - og + categories: + - olm + scope: Namespaced + subresources: + # status enables the status subresource. + status: {} + validation: + openAPIV3Schema: + properties: + spec: + properties: + selector: + type: object + description: Optional label selector to find resources associated with or managed by the operator + properties: + matchLabels: + type: object + description: Label key:value pairs to match directly + matchExpressions: + type: array + description: A set of expressions to match against the resource. + items: + allOf: + - type: object + required: + - key + - operator + - values + properties: + key: + type: string + description: the key to match + operator: + type: string + description: the operator for the expression + enum: + - In + - NotIn + - Exists + - DoesNotExist + values: + type: array + description: set of values for the expression + targetNamespaces: + type: array + description: Optional list of target namespaces. If set, OLM will ignore selector. + items: + type: string + pattern: ^\S+$ + serviceAccountName: + type: string + staticProvidedAPIs: + type: boolean + description: If true, OLM will not modify the OperatorGroup's providedAPIs annotation. + type: object + status: + properties: + lastUpdated: + format: date-time + type: string + namespaces: + items: + type: string + type: array + required: + - namespaces + - lastUpdated + type: object + required: + - metadata \ No newline at end of file diff --git a/deploy/upstream/manifests/0.8.1/0000_50_14-olm-operators.configmap.yaml b/deploy/upstream/manifests/0.8.1/0000_50_10-olm-operators.configmap.yaml similarity index 98% rename from deploy/upstream/manifests/0.8.1/0000_50_14-olm-operators.configmap.yaml rename to deploy/upstream/manifests/0.8.1/0000_50_10-olm-operators.configmap.yaml index d574e57e43..cdb2338492 100644 --- a/deploy/upstream/manifests/0.8.1/0000_50_14-olm-operators.configmap.yaml +++ b/deploy/upstream/manifests/0.8.1/0000_50_10-olm-operators.configmap.yaml @@ -1,5 +1,5 @@ -##--- -# Source: olm/templates/0000_50_14-olm-operators.configmap.yaml +--- +# Source: olm/templates/0000_50_10-olm-operators.configmap.yaml kind: ConfigMap apiVersion: v1 metadata: diff --git a/deploy/upstream/manifests/0.8.1/0000_50_15-olm-operators.catalogsource.yaml b/deploy/upstream/manifests/0.8.1/0000_50_11-olm-operators.catalogsource.yaml similarity index 81% rename from deploy/upstream/manifests/0.8.1/0000_50_15-olm-operators.catalogsource.yaml rename to deploy/upstream/manifests/0.8.1/0000_50_11-olm-operators.catalogsource.yaml index 170f608e04..5521aeb30e 100644 --- a/deploy/upstream/manifests/0.8.1/0000_50_15-olm-operators.catalogsource.yaml +++ b/deploy/upstream/manifests/0.8.1/0000_50_11-olm-operators.catalogsource.yaml @@ -1,5 +1,5 @@ -##--- -# Source: olm/templates/0000_50_15-olm-operators.catalogsource.yaml +--- +# Source: olm/templates/0000_50_11-olm-operators.catalogsource.yaml #! validate-crd: ./deploy/chart/templates/05-catalogsource.crd.yaml #! parse-kind: CatalogSource apiVersion: operators.coreos.com/v1alpha1 diff --git a/deploy/upstream/manifests/0.8.1/0000_50_16-operatorgroup-default.yaml b/deploy/upstream/manifests/0.8.1/0000_50_12-operatorgroup-default.yaml similarity index 79% rename from deploy/upstream/manifests/0.8.1/0000_50_16-operatorgroup-default.yaml rename to deploy/upstream/manifests/0.8.1/0000_50_12-operatorgroup-default.yaml index 1a16653606..ff4e2715c4 100644 --- a/deploy/upstream/manifests/0.8.1/0000_50_16-operatorgroup-default.yaml +++ b/deploy/upstream/manifests/0.8.1/0000_50_12-operatorgroup-default.yaml @@ -1,5 +1,5 @@ -##--- -# Source: olm/templates/0000_50_16-operatorgroup-default.yaml +--- +# Source: olm/templates/0000_50_12-operatorgroup-default.yaml apiVersion: operators.coreos.com/v1alpha2 kind: OperatorGroup metadata: diff --git a/deploy/upstream/manifests/0.8.1/0000_50_17-packageserver.subscription.yaml b/deploy/upstream/manifests/0.8.1/0000_50_13-packageserver.subscription.yaml similarity index 81% rename from deploy/upstream/manifests/0.8.1/0000_50_17-packageserver.subscription.yaml rename to deploy/upstream/manifests/0.8.1/0000_50_13-packageserver.subscription.yaml index 10cbf1c19c..3262d3e60e 100644 --- a/deploy/upstream/manifests/0.8.1/0000_50_17-packageserver.subscription.yaml +++ b/deploy/upstream/manifests/0.8.1/0000_50_13-packageserver.subscription.yaml @@ -1,5 +1,5 @@ -##--- -# Source: olm/templates/0000_50_17-packageserver.subscription.yaml +--- +# Source: olm/templates/0000_50_13-packageserver.subscription.yaml #! validate-crd: ./deploy/chart/templates/04-subscription.crd.yaml #! parse-kind: Subscription apiVersion: operators.coreos.com/v1alpha1 diff --git a/deploy/upstream/manifests/0.8.1/0000_50_18-operatorstatus.yaml b/deploy/upstream/manifests/0.8.1/0000_50_18-operatorstatus.yaml deleted file mode 100644 index 668d682b01..0000000000 --- a/deploy/upstream/manifests/0.8.1/0000_50_18-operatorstatus.yaml +++ /dev/null @@ -1,6 +0,0 @@ -##--- -# Source: olm/templates/0000_50_18-operatorstatus.yaml -apiVersion: config.openshift.io/v1 -kind: ClusterOperator -metadata: - name: operator-lifecycle-manager diff --git a/deploy/upstream/values.yaml b/deploy/upstream/values.yaml index e4c4d6b0e7..c6be2167c6 100644 --- a/deploy/upstream/values.yaml +++ b/deploy/upstream/values.yaml @@ -26,4 +26,4 @@ package: service: internalPort: 5443 catalog_sources: -- rh-operators \ No newline at end of file +- rh-operators diff --git a/go.mod b/go.mod index 064c254d36..1d68fe02bf 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/coreos/go-semver v0.2.0 github.com/coreos/go-systemd v0.0.0-20190204112023-081494f7ee4f // indirect github.com/docker/distribution v2.7.1+incompatible // indirect + github.com/emicklei/go-restful v2.9.0+incompatible // indirect github.com/ghodss/yaml v1.0.0 github.com/go-openapi/spec v0.17.2 github.com/gogo/protobuf v1.2.0 // indirect @@ -32,12 +33,13 @@ require ( google.golang.org/grpc v1.16.0 k8s.io/api v0.0.0-20190118113203-912cbe2bfef3 k8s.io/apiextensions-apiserver v0.0.0-20181204003618-e419c5771cdc - k8s.io/apimachinery v0.0.0-20190118094746-1525e4dadd2d + k8s.io/apimachinery v0.0.0-20190208202428-1a579f8a7b42 k8s.io/apiserver v0.0.0-20181026151315-13cfe3978170 k8s.io/client-go v8.0.0+incompatible k8s.io/code-generator v0.0.0-20181203235156-f8cba74510f3 k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6 // indirect + k8s.io/klog v0.2.0 // indirect k8s.io/kube-aggregator v0.0.0-20181204002017-122bac39d429 k8s.io/kube-openapi v0.0.0-20181031203759-72693cb1fadd - k8s.io/kubernetes v1.11.8-beta.0.0.20190131222539-8546c0ceb197 + k8s.io/kubernetes v1.11.8-beta.0.0.20190208223919-e6f6fa1f2dd1 ) diff --git a/go.sum b/go.sum index 1aa852fb96..a318b3c13f 100644 --- a/go.sum +++ b/go.sum @@ -22,12 +22,11 @@ github.com/coreos/bbolt v1.3.2 h1:wZwiHHUieZCquLkDL0B8UhzreNWsPHooDAG3q34zk0s= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.9+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/etcd v3.3.11+incompatible h1:0gCnqKsq7XxMi69JsnbmMc1o+RJH3XH64sV9aiTTYko= +github.com/coreos/etcd v3.3.11+incompatible h1:U0wJghY374q+UrjOM2mfROHSwEspsQVkCACB1PGka1g= github.com/coreos/etcd v3.3.11+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.2.0 h1:3Jm3tLmsgAYcjC+4Up7hJrFBPr+n7rAqYeSw/SZazuY= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20181031085051-9002847aa142 h1:3jFq2xL4ZajGK4aZY8jz+DAF0FHjI51BXjjSwCzS1Dk= github.com/coreos/go-systemd v0.0.0-20181031085051-9002847aa142/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190204112023-081494f7ee4f h1:aRjCy0NoKF4dCFsSq2oosyXbviroYKuZxb9JIJxRmGs= github.com/coreos/go-systemd v0.0.0-20190204112023-081494f7ee4f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= @@ -44,8 +43,9 @@ github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4Kfc github.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3CKKpKinvZLFk= github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= github.com/emicklei/go-restful v2.8.0+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful v2.8.1+incompatible h1:AyDqLHbJ1quqbWr/OWDw+PlIP8ZFoTmYrGYaxzrLbNg= github.com/emicklei/go-restful v2.8.1+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.9.0+incompatible h1:YKhDcF/NL19iSAQcyCATL1MkFXCzxfdaTiuJKr18Ank= +github.com/emicklei/go-restful v2.9.0+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful-swagger12 v0.0.0-20170926063155-7524189396c6 h1:V94anc0ZG3Pa/cAMwP2m1aQW3+/FF8Qmw/GsFyTJAp4= github.com/emicklei/go-restful-swagger12 v0.0.0-20170926063155-7524189396c6/go.mod h1:qr0VowGBT4CS4Q8vFF8BSeKz34PuqKGxs/L0IAQA9DQ= github.com/evanphx/json-patch v3.0.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= @@ -112,7 +112,7 @@ github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoA github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f h1:ShTPMJQes6tubcjzGMODIVG5hlrCeImaBnZzKF2N8SM= github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:BWIsLfhgKhV5g/oF34aRjniBHLTZe5DNekSjbAjIS6c= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= @@ -294,8 +294,9 @@ k8s.io/apiextensions-apiserver v0.0.0-20181204003618-e419c5771cdc h1:IOukeE9HtTw k8s.io/apiextensions-apiserver v0.0.0-20181204003618-e419c5771cdc/go.mod h1:IxkesAMoaCRoLrPJdZNZUQp9NfZnzqaVzLhb2VEQzXE= k8s.io/apimachinery v0.0.0-20180904193909-def12e63c512/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= k8s.io/apimachinery v0.0.0-20181203235515-3d8ee2261517/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= -k8s.io/apimachinery v0.0.0-20190118094746-1525e4dadd2d h1:LfIlK5wCRzbVZ1WVwcabYwBTKZJehDSBWq9Xf1S5g+o= k8s.io/apimachinery v0.0.0-20190118094746-1525e4dadd2d/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= +k8s.io/apimachinery v0.0.0-20190208202428-1a579f8a7b42 h1:ju2lLx7i6XE8A9QLtwxlQngelP8SosMIjK4IYE/TLFI= +k8s.io/apimachinery v0.0.0-20190208202428-1a579f8a7b42/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= k8s.io/apiserver v0.0.0-20181026151315-13cfe3978170 h1:CqI85nZvPaV+7JFono0nAOGOx2brocqefcOhDPVhHKI= k8s.io/apiserver v0.0.0-20181026151315-13cfe3978170/go.mod h1:6bqaTSOSJavUIXUtfaR9Os9JtTCm8ZqH2SUl2S60C4w= k8s.io/client-go v0.0.0-20180718001006-59698c7d9724/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= @@ -308,13 +309,14 @@ k8s.io/gengo v0.0.0-20181113154421-fd15ee9cc2f7/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8 k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6 h1:4s3/R4+OYYYUKptXPhZKjQ04WJ6EhQQVFdjOFvCazDk= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/klog v0.1.0 h1:I5HMfc/DtuVaGR1KPwUrTc476K8NCqNBldC7H4dYEzk= k8s.io/klog v0.1.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.2.0 h1:0ElL0OHzF3N+OhoJTL0uca20SxtYt4X4+bzHeqrB83c= +k8s.io/klog v0.2.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/kube-aggregator v0.0.0-20181204002017-122bac39d429 h1:wIDPKpRuwEfyt+ImBaP6wSEZeAR5gYJl/Mlg74L0hHI= k8s.io/kube-aggregator v0.0.0-20181204002017-122bac39d429/go.mod h1:8sbzT4QQKDEmSCIbfqjV0sd97GpUT7A4W626sBiYJmU= k8s.io/kube-openapi v0.0.0-20181031203759-72693cb1fadd h1:ggv/Vfza0i5xuhUZyYyxcc25AmQvHY8Zi1C2m8WgBvA= k8s.io/kube-openapi v0.0.0-20181031203759-72693cb1fadd/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= k8s.io/kubernetes v1.11.7-beta.0.0.20181219023948-b875d52ea96d/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= k8s.io/kubernetes v1.11.8-beta.0.0.20190124204751-3a10094374f2/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= -k8s.io/kubernetes v1.11.8-beta.0.0.20190131222539-8546c0ceb197 h1:d7vjTpFZpS8a7W1DW663euMlG/d0F1Y20le0HC0Wuxw= -k8s.io/kubernetes v1.11.8-beta.0.0.20190131222539-8546c0ceb197/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= +k8s.io/kubernetes v1.11.8-beta.0.0.20190208223919-e6f6fa1f2dd1 h1:7vUXpxFc5F5qLOKT07j2EZa1M+6xl6Es2WyRxleFCeM= +k8s.io/kubernetes v1.11.8-beta.0.0.20190208223919-e6f6fa1f2dd1/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= diff --git a/manifests/0000_50_olm_00-namespace.yaml b/manifests/0000_50_00-namespace.yaml similarity index 100% rename from manifests/0000_50_olm_00-namespace.yaml rename to manifests/0000_50_00-namespace.yaml diff --git a/manifests/0000_50_olm_01-olm-operator.serviceaccount.yaml b/manifests/0000_50_01-olm-operator.serviceaccount.yaml similarity index 100% rename from manifests/0000_50_olm_01-olm-operator.serviceaccount.yaml rename to manifests/0000_50_01-olm-operator.serviceaccount.yaml diff --git a/manifests/0000_50_olm_02-clusterserviceversion.crd.yaml b/manifests/0000_50_02-clusterserviceversion.crd.yaml similarity index 100% rename from manifests/0000_50_olm_02-clusterserviceversion.crd.yaml rename to manifests/0000_50_02-clusterserviceversion.crd.yaml diff --git a/manifests/0000_50_olm_03-installplan.crd.yaml b/manifests/0000_50_03-installplan.crd.yaml similarity index 100% rename from manifests/0000_50_olm_03-installplan.crd.yaml rename to manifests/0000_50_03-installplan.crd.yaml diff --git a/manifests/0000_50_olm_04-subscription.crd.yaml b/manifests/0000_50_04-subscription.crd.yaml similarity index 100% rename from manifests/0000_50_olm_04-subscription.crd.yaml rename to manifests/0000_50_04-subscription.crd.yaml diff --git a/manifests/0000_50_olm_05-catalogsource.crd.yaml b/manifests/0000_50_05-catalogsource.crd.yaml similarity index 100% rename from manifests/0000_50_olm_05-catalogsource.crd.yaml rename to manifests/0000_50_05-catalogsource.crd.yaml diff --git a/manifests/0000_50_olm_10-olm-operator.deployment.yaml b/manifests/0000_50_06-olm-operator.deployment.yaml similarity index 100% rename from manifests/0000_50_olm_10-olm-operator.deployment.yaml rename to manifests/0000_50_06-olm-operator.deployment.yaml diff --git a/manifests/0000_50_olm_11-catalog-operator.deployment.yaml b/manifests/0000_50_07-catalog-operator.deployment.yaml similarity index 100% rename from manifests/0000_50_olm_11-catalog-operator.deployment.yaml rename to manifests/0000_50_07-catalog-operator.deployment.yaml diff --git a/manifests/0000_50_olm_12-aggregated.clusterrole.yaml b/manifests/0000_50_08-aggregated.clusterrole.yaml similarity index 100% rename from manifests/0000_50_olm_12-aggregated.clusterrole.yaml rename to manifests/0000_50_08-aggregated.clusterrole.yaml diff --git a/deploy/upstream/manifests/0.8.1/0000_50_13-operatorgroup.crd.yaml b/manifests/0000_50_09-operatorgroup.crd.yaml similarity index 86% rename from deploy/upstream/manifests/0.8.1/0000_50_13-operatorgroup.crd.yaml rename to manifests/0000_50_09-operatorgroup.crd.yaml index 8faa1bb107..18d92e2f79 100644 --- a/deploy/upstream/manifests/0.8.1/0000_50_13-operatorgroup.crd.yaml +++ b/manifests/0000_50_09-operatorgroup.crd.yaml @@ -1,5 +1,3 @@ -##--- -# Source: olm/templates/0000_50_13-operatorgroup.crd.yaml apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: @@ -31,7 +29,7 @@ spec: properties: selector: type: object - description: Label selector to find resources associated with or managed by the operator + description: Optional label selector to find resources associated with or managed by the operator properties: matchLabels: type: object @@ -63,11 +61,15 @@ spec: description: set of values for the expression targetNamespaces: type: array + description: Optional list of target namespaces. If set, OLM will ignore selector. items: type: string pattern: ^\S+$ serviceAccountName: type: string + staticProvidedAPIs: + type: boolean + description: If true, OLM will not modify the OperatorGroup's providedAPIs annotation. type: object status: properties: @@ -83,4 +85,4 @@ spec: - lastUpdated type: object required: - - metadata + - metadata \ No newline at end of file diff --git a/manifests/0000_50_olm_14-olm-operators.configmap.yaml b/manifests/0000_50_10-olm-operators.configmap.yaml similarity index 100% rename from manifests/0000_50_olm_14-olm-operators.configmap.yaml rename to manifests/0000_50_10-olm-operators.configmap.yaml diff --git a/manifests/0000_50_olm_15-olm-operators.catalogsource.yaml b/manifests/0000_50_11-olm-operators.catalogsource.yaml similarity index 100% rename from manifests/0000_50_olm_15-olm-operators.catalogsource.yaml rename to manifests/0000_50_11-olm-operators.catalogsource.yaml diff --git a/manifests/0000_50_olm_16-operatorgroup-default.yaml b/manifests/0000_50_12-operatorgroup-default.yaml similarity index 100% rename from manifests/0000_50_olm_16-operatorgroup-default.yaml rename to manifests/0000_50_12-operatorgroup-default.yaml diff --git a/manifests/0000_50_olm_17-packageserver.subscription.yaml b/manifests/0000_50_13-packageserver.subscription.yaml similarity index 100% rename from manifests/0000_50_olm_17-packageserver.subscription.yaml rename to manifests/0000_50_13-packageserver.subscription.yaml diff --git a/manifests/0000_50_olm_18-operatorstatus.yaml b/manifests/0000_50_14-operatorstatus.yaml similarity index 100% rename from manifests/0000_50_olm_18-operatorstatus.yaml rename to manifests/0000_50_14-operatorstatus.yaml diff --git a/manifests/0000_50_olm_13-operatorgroup.crd.yaml b/manifests/0000_50_olm_13-operatorgroup.crd.yaml deleted file mode 100644 index dd2a905083..0000000000 --- a/manifests/0000_50_olm_13-operatorgroup.crd.yaml +++ /dev/null @@ -1,84 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: operatorgroups.operators.coreos.com -spec: - group: operators.coreos.com - version: v1alpha2 - versions: - - name: v1alpha2 - served: true - storage: true - names: - plural: operatorgroups - singular: operatorgroup - kind: OperatorGroup - listKind: OperatorGroupList - shortNames: - - og - categories: - - olm - scope: Namespaced - subresources: - # status enables the status subresource. - status: {} - validation: - openAPIV3Schema: - properties: - spec: - properties: - selector: - type: object - description: Label selector to find resources associated with or managed by the operator - properties: - matchLabels: - type: object - description: Label key:value pairs to match directly - matchExpressions: - type: array - description: A set of expressions to match against the resource. - items: - allOf: - - type: object - required: - - key - - operator - - values - properties: - key: - type: string - description: the key to match - operator: - type: string - description: the operator for the expression - enum: - - In - - NotIn - - Exists - - DoesNotExist - values: - type: array - description: set of values for the expression - targetNamespaces: - type: array - items: - type: string - pattern: ^\S+$ - serviceAccountName: - type: string - type: object - status: - properties: - lastUpdated: - format: date-time - type: string - namespaces: - items: - type: string - type: array - required: - - namespaces - - lastUpdated - type: object - required: - - metadata diff --git a/pkg/api/apis/operators/v1alpha1/clusterserviceversion_types.go b/pkg/api/apis/operators/v1alpha1/clusterserviceversion_types.go index b2bfa613cb..e32a9aefe2 100644 --- a/pkg/api/apis/operators/v1alpha1/clusterserviceversion_types.go +++ b/pkg/api/apis/operators/v1alpha1/clusterserviceversion_types.go @@ -223,29 +223,31 @@ const ( type ConditionReason string const ( - CSVReasonRequirementsUnknown ConditionReason = "RequirementsUnknown" - CSVReasonRequirementsNotMet ConditionReason = "RequirementsNotMet" - CSVReasonRequirementsMet ConditionReason = "AllRequirementsMet" - CSVReasonOwnerConflict ConditionReason = "OwnerConflict" - CSVReasonComponentFailed ConditionReason = "InstallComponentFailed" - CSVReasonInvalidStrategy ConditionReason = "InvalidInstallStrategy" - CSVReasonWaiting ConditionReason = "InstallWaiting" - CSVReasonInstallSuccessful ConditionReason = "InstallSucceeded" - CSVReasonInstallCheckFailed ConditionReason = "InstallCheckFailed" - CSVReasonComponentUnhealthy ConditionReason = "ComponentUnhealthy" - CSVReasonBeingReplaced ConditionReason = "BeingReplaced" - CSVReasonReplaced ConditionReason = "Replaced" - CSVReasonNeedsReinstall ConditionReason = "NeedsReinstall" - CSVReasonNeedsCertRotation ConditionReason = "NeedsCertRotation" - CSVReasonAPIServiceResourceIssue ConditionReason = "APIServiceResourceIssue" - CSVReasonAPIServiceResourcesNeedReinstall ConditionReason = "APIServiceResourcesNeedReinstall" - CSVReasonAPIServiceInstallFailed ConditionReason = "APIServiceInstallFailed" - CSVReasonCopied ConditionReason = "Copied" - CSVReasonInvalidInstallModes ConditionReason = "InvalidInstallModes" - CSVReasonNoTargetNamespaces ConditionReason = "NoTargetNamespaces" - CSVReasonUnsupportedOperatorGroup ConditionReason = "UnsupportedOperatorGroup" - CSVReasonNoOperatorGroup ConditionReason = "NoOperatorGroup" - CSVReasonTooManyOperatorGroups ConditionReason = "TooManyOperatorGroups" + CSVReasonRequirementsUnknown ConditionReason = "RequirementsUnknown" + CSVReasonRequirementsNotMet ConditionReason = "RequirementsNotMet" + CSVReasonRequirementsMet ConditionReason = "AllRequirementsMet" + CSVReasonOwnerConflict ConditionReason = "OwnerConflict" + CSVReasonComponentFailed ConditionReason = "InstallComponentFailed" + CSVReasonInvalidStrategy ConditionReason = "InvalidInstallStrategy" + CSVReasonWaiting ConditionReason = "InstallWaiting" + CSVReasonInstallSuccessful ConditionReason = "InstallSucceeded" + CSVReasonInstallCheckFailed ConditionReason = "InstallCheckFailed" + CSVReasonComponentUnhealthy ConditionReason = "ComponentUnhealthy" + CSVReasonBeingReplaced ConditionReason = "BeingReplaced" + CSVReasonReplaced ConditionReason = "Replaced" + CSVReasonNeedsReinstall ConditionReason = "NeedsReinstall" + CSVReasonNeedsCertRotation ConditionReason = "NeedsCertRotation" + CSVReasonAPIServiceResourceIssue ConditionReason = "APIServiceResourceIssue" + CSVReasonAPIServiceResourcesNeedReinstall ConditionReason = "APIServiceResourcesNeedReinstall" + CSVReasonAPIServiceInstallFailed ConditionReason = "APIServiceInstallFailed" + CSVReasonCopied ConditionReason = "Copied" + CSVReasonInvalidInstallModes ConditionReason = "InvalidInstallModes" + CSVReasonNoTargetNamespaces ConditionReason = "NoTargetNamespaces" + CSVReasonUnsupportedOperatorGroup ConditionReason = "UnsupportedOperatorGroup" + CSVReasonNoOperatorGroup ConditionReason = "NoOperatorGroup" + CSVReasonTooManyOperatorGroups ConditionReason = "TooManyOperatorGroups" + CSVReasonInterOperatorGroupOwnerConflict ConditionReason = "InterOperatorGroupOwnerConflict" + CSVReasonCannotModifyStaticOperatorGroupProvidedAPIs ConditionReason = "CannotModifyStaticOperatorGroupProvidedAPIs" ) // Conditions appear in the status as a record of state transitions on the ClusterServiceVersion diff --git a/pkg/api/apis/operators/v1alpha2/operatorgroup_types.go b/pkg/api/apis/operators/v1alpha2/operatorgroup_types.go index 07d76d33a8..c3867f9c28 100644 --- a/pkg/api/apis/operators/v1alpha2/operatorgroup_types.go +++ b/pkg/api/apis/operators/v1alpha2/operatorgroup_types.go @@ -17,10 +17,20 @@ type OperatorGroupSpec struct { // ServiceAccount to bind OperatorGroup roles to. ServiceAccount corev1.ServiceAccount `json:"serviceAccount,omitempty"` + + // Static tells OLM not to update the OperatorGroup's providedAPIs annotation + // +optional + StaticProvidedAPIs bool `json:"staticProvidedAPIs,omitempty"` } type OperatorGroupStatus struct { - Namespaces []string `json:"namespaces,omitempty"` + // Namespaces is the set of target namespaces for the OperatorGroup. + Namespaces []string `json:"namespaces,omitempty"` + + // ProvidedAPIs represents the set of APIs provided by the OperatorGroup's member CSVs. + // ProvidedAPIs []metav1.TypeMeta `json:"providedAPIs,omitempty"` + + // LastUpdated is a timestamp of the last time the OperatorGroup's status was Updated. LastUpdated metav1.Time `json:"lastUpdated"` } @@ -43,7 +53,8 @@ type OperatorGroupList struct { } const ( - OperatorGroupAnnotationKey = "olm.operatorGroup" - OperatorGroupNamespaceAnnotationKey = "olm.operatorNamespace" - OperatorGroupTargetsAnnotationKey = "olm.targetNamespaces" + OperatorGroupAnnotationKey = "olm.operatorGroup" + OperatorGroupNamespaceAnnotationKey = "olm.operatorNamespace" + OperatorGroupTargetsAnnotationKey = "olm.targetNamespaces" + OperatorGroupProvidedAPIsAnnotationKey = "olm.providedAPIs" ) diff --git a/pkg/controller/operators/olm/operator.go b/pkg/controller/operators/olm/operator.go index 01c3bd5dca..d22d9c78fd 100644 --- a/pkg/controller/operators/olm/operator.go +++ b/pkg/controller/operators/olm/operator.go @@ -19,7 +19,8 @@ import ( "k8s.io/client-go/tools/record" "k8s.io/client-go/util/workqueue" kagg "k8s.io/kube-aggregator/pkg/client/informers/externalversions" - + + "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver" "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1" "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha2" "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned" @@ -49,13 +50,15 @@ const ( type Operator struct { *queueinformer.Operator csvQueueSet queueinformer.ResourceQueueSet + ogQueueSet queueinformer.ResourceQueueSet client versioned.Interface resolver install.StrategyResolverInterface + apiReconciler resolver.APIIntersectionReconciler lister operatorlister.OperatorLister recorder record.EventRecorder } -func NewOperator(logger *logrus.Logger, crClient versioned.Interface, opClient operatorclient.ClientInterface, resolver install.StrategyResolverInterface, wakeupInterval time.Duration, namespaces []string) (*Operator, error) { +func NewOperator(logger *logrus.Logger, crClient versioned.Interface, opClient operatorclient.ClientInterface, strategyResolver install.StrategyResolverInterface, wakeupInterval time.Duration, namespaces []string) (*Operator, error) { if wakeupInterval < 0 { wakeupInterval = FallbackWakeupInterval } @@ -74,10 +77,12 @@ func NewOperator(logger *logrus.Logger, crClient versioned.Interface, opClient o op := &Operator{ Operator: queueOperator, - csvQueueSet: make(map[string]workqueue.RateLimitingInterface), + csvQueueSet: make(queueinformer.ResourceQueueSet), + ogQueueSet: make(queueinformer.ResourceQueueSet), client: crClient, + resolver: strategyResolver, + apiReconciler: resolver.APIIntersectionReconcileFunc(resolver.ReconcileAPIIntersection), lister: operatorlister.NewLister(), - resolver: resolver, recorder: eventRecorder, } @@ -225,7 +230,7 @@ func NewOperator(logger *logrus.Logger, crClient versioned.Interface, opClient o // csvInformers for each namespace all use the same backing queue keys are namespaced csvHandlers := &cache.ResourceEventHandlerFuncs{ - DeleteFunc: op.deleteClusterServiceVersion, + DeleteFunc: op.handleClusterServiceVersionDeletion, } for _, namespace := range namespaces { logger.WithField("namespace", namespace).Infof("watching CSVs") @@ -243,6 +248,7 @@ func NewOperator(logger *logrus.Logger, crClient versioned.Interface, opClient o // Set up watch on deployments depHandlers := &cache.ResourceEventHandlerFuncs{ + // TODO: pass closure that forgets queue item after calling custom deletion handler. DeleteFunc: op.handleDeletion, } for _, namespace := range namespaces { @@ -269,6 +275,7 @@ func NewOperator(logger *logrus.Logger, crClient versioned.Interface, opClient o operatorGroupQueue := workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), queueName) operatorGroupQueueInformer := queueinformer.NewInformer(operatorGroupQueue, operatorGroupInformer.Informer(), op.syncOperatorGroups, nil, queueName, metrics.NewMetricsNil(), logger) op.RegisterQueueInformer(operatorGroupQueueInformer) + op.ogQueueSet[namespace] = operatorGroupQueue } return op, nil @@ -313,11 +320,20 @@ func (a *Operator) syncObject(obj interface{}) (syncError error) { return nil } -func (a *Operator) deleteClusterServiceVersion(obj interface{}) { +func (a *Operator) handleClusterServiceVersionDeletion(obj interface{}) { clusterServiceVersion, ok := obj.(*v1alpha1.ClusterServiceVersion) if !ok { - a.Log.Debugf("wrong type: %#v", obj) - return + tombstone, ok := obj.(cache.DeletedFinalStateUnknown) + if !ok { + utilruntime.HandleError(fmt.Errorf("Couldn't get object from tombstone %#v", obj)) + return + } + + clusterServiceVersion, ok = tombstone.Obj.(*v1alpha1.ClusterServiceVersion) + if !ok { + utilruntime.HandleError(fmt.Errorf("Tombstone contained object that is not a ClusterServiceVersion %#v", obj)) + return + } } logger := a.Log.WithFields(logrus.Fields{ @@ -327,25 +343,63 @@ func (a *Operator) deleteClusterServiceVersion(obj interface{}) { "phase": clusterServiceVersion.Status.Phase, }) + defer func(csv v1alpha1.ClusterServiceVersion) { + logger.Debug("removing csv from queue set") + a.csvQueueSet.Remove(csv.GetName(), csv.GetNamespace()) + }(*clusterServiceVersion) + targetNamespaces, ok := clusterServiceVersion.Annotations[v1alpha2.OperatorGroupTargetsAnnotationKey] if !ok { - logger.Debugf("Ignoring CSV with no annotation") + logger.Debug("missing target namespaces annotation on csv") + return } operatorNamespace, ok := clusterServiceVersion.Annotations[v1alpha2.OperatorGroupNamespaceAnnotationKey] if !ok { - logger.Debugf("missing operator namespace annotation on CSV") + logger.Debug("missing operator namespace annotation on csv") + return + } + + operatorGroupName, ok := clusterServiceVersion.Annotations[v1alpha2.OperatorGroupAnnotationKey] + if !ok { + logger.Debug("missing operatorgroup name annotation on csv") + return } if clusterServiceVersion.Status.Reason == v1alpha1.CSVReasonCopied { + logger.Debug("deleted csv is copied. skipping additional cleanup steps") return } - logger.Info("parent CSV deleted, GC children") - for _, namespace := range strings.Split(targetNamespaces, ",") { + + logger = logger.WithField("operatorgroup", operatorGroupName) + logger.Info("active csv deleted, removing providedAPIs from operatorgroup annotations") + if operatorGroup, _ := a.lister.OperatorsV1alpha2().OperatorGroupLister().OperatorGroups(operatorNamespace).Get(operatorGroupName); operatorGroup != nil { + logger.Debug("requeueing") + if err := a.ogQueueSet.Requeue(operatorGroup.GetName(), operatorGroup.GetNamespace()); err != nil { + logger.WithError(err).Debug("error requeueing") + } + } else { + logger.Debug("operatorgroup not found during csv deletion") + } + + logger.Info("gcing children") + namespaces := []string{} + if targetNamespaces == "" { + namespaceList, err := a.OpClient.KubernetesInterface().CoreV1().Namespaces().List(metav1.ListOptions{}) + if err != nil { + logger.WithError(err).Warn("cannot list all namespaces to requeue child csvs for deletion") + return + } + for _, namespace := range namespaceList.Items { + namespaces = append(namespaces, namespace.GetName()) + } + } else { + namespaces = strings.Split(targetNamespaces, ",") + } + for _, namespace := range namespaces { if namespace != operatorNamespace { - if err := a.client.OperatorsV1alpha1().ClusterServiceVersions(namespace).Delete(clusterServiceVersion.GetName(), &metav1.DeleteOptions{}); err != nil { - logger.WithError(err).Debug("error deleting child CSV") - } + logger.WithField("targetNamespace", namespace).Debug("requeueing child csv for deletion") + a.csvQueueSet.Requeue(clusterServiceVersion.GetName(), namespace) } } } @@ -361,16 +415,26 @@ func (a *Operator) removeDanglingChildCSVs(csv *v1alpha1.ClusterServiceVersion) operatorNamespace, ok := csv.Annotations[v1alpha2.OperatorGroupNamespaceAnnotationKey] if !ok { logger.Debug("missing operator namespace annotation on copied CSV") + // TODO: Should we clean up the CSV if we don't know where its parent is? return fmt.Errorf("missing operator namespace annotation on copied CSV") } - _, err := a.lister.OperatorsV1alpha1().ClusterServiceVersionLister().ClusterServiceVersions(operatorNamespace).Get(csv.GetName()) + delete := false + parent, err := a.lister.OperatorsV1alpha1().ClusterServiceVersionLister().ClusterServiceVersions(operatorNamespace).Get(csv.GetName()) if k8serrors.IsNotFound(err) || k8serrors.IsGone(err) { - logger.Debug("deleting CSV since parent is missing") + logger.Debug("deleting copied CSV since parent is missing") + delete = true + } else if parent != nil && parent.Status.Phase == v1alpha1.CSVPhaseFailed && parent.Status.Reason == v1alpha1.CSVReasonInterOperatorGroupOwnerConflict { + logger.Debug("deleting copied CSV since parent has intersecting operatorgroup conflict") + delete = true + } + + if delete { if err := a.client.OperatorsV1alpha1().ClusterServiceVersions(csv.GetNamespace()).Delete(csv.GetName(), &metav1.DeleteOptions{}); err != nil { return err } } + return nil } @@ -413,6 +477,12 @@ func (a *Operator) syncClusterServiceVersion(obj interface{}) (syncError error) operatorGroup := a.operatorGroupForActiveCSV(logger, updatedCSV) if operatorGroup == nil { + logger.WithField("reason", "no operatorgroup found for active CSV").Info("skipping CSV resource copy to target namespaces") + return + } + + if updatedCSV.Status.Phase == v1alpha1.CSVPhaseFailed && updatedCSV.Status.Reason == v1alpha1.CSVReasonInterOperatorGroupOwnerConflict { + logger.WithField("reason", updatedCSV.Status.Message).Info("skipping CSV resource copy to target namespaces") return } @@ -506,6 +576,7 @@ func (a *Operator) transitionCSVState(in v1alpha1.ClusterServiceVersion) (out *v logger.Errorf("error occurred while attempting to associate csv with operatorgroup") syncError = err } + var operatorGroup *v1alpha2.OperatorGroup switch len(operatorGroups) { case 0: @@ -514,7 +585,8 @@ func (a *Operator) transitionCSVState(in v1alpha1.ClusterServiceVersion) (out *v out.SetPhaseWithEvent(v1alpha1.CSVPhaseFailed, v1alpha1.CSVReasonNoOperatorGroup, syncError.Error(), now, a.recorder) return case 1: - if operatorGroup := a.operatorGroupForActiveCSV(logger, out); operatorGroup == nil { + operatorGroup = a.operatorGroupForActiveCSV(logger, out) + if operatorGroup == nil { operatorGroup = operatorGroups[0] logger = logger.WithField("opgroup", operatorGroup.GetName()) @@ -553,11 +625,77 @@ func (a *Operator) transitionCSVState(in v1alpha1.ClusterServiceVersion) (out *v return } } else { - // TODO: This should never be the case. + // This should never be the case logger.Info("no targets annotation defined for CSV") out.SetPhaseWithEvent(v1alpha1.CSVPhaseFailed, v1alpha1.CSVReasonNoTargetNamespaces, err.Error(), now, a.recorder) } + // Check for intersecting provided APIs in intersecting OperatorGroups + options := metav1.ListOptions{ + FieldSelector: fmt.Sprintf("metadata.name!=%s,metadata.namespace!=%s", operatorGroup.GetName(), operatorGroup.GetNamespace()), + } + otherGroups, err := a.client.OperatorsV1alpha2().OperatorGroups(metav1.NamespaceAll).List(options) + + groupSurface := resolver.NewOperatorGroup(*operatorGroup) + otherGroupSurfaces := resolver.NewOperatorGroupSurfaces(otherGroups.Items...) + + operatorSurface, err := resolver.NewOperatorFromCSV(out) + if err != nil { + // TODO: Add failure status to CSV + syncError = err + return + } + providedAPIs := operatorSurface.ProvidedAPIs().StripPlural() + + switch result := a.apiReconciler.Reconcile(providedAPIs, groupSurface, otherGroupSurfaces...); { + case operatorGroup.Spec.StaticProvidedAPIs && (result == resolver.AddAPIs || result == resolver.RemoveAPIs): + // Transition the CSV to FAILED with status reason "CannotModifyStaticOperatorGroupProvidedAPIs" + if out.Status.Reason != v1alpha1.CSVReasonInterOperatorGroupOwnerConflict { + logger.WithField("apis", providedAPIs).Warn("cannot modify provided apis of static provided api operatorgroup") + out.SetPhaseWithEvent(v1alpha1.CSVPhaseFailed, v1alpha1.CSVReasonCannotModifyStaticOperatorGroupProvidedAPIs, "static provided api operatorgroup cannot be modified by these apis", now, a.recorder) + a.cleanupCSVDeployments(logger, out) + } + return + case result == resolver.APIConflict: + // Transition the CSV to FAILED with status reason "InterOperatorGroupOwnerConflict" + if out.Status.Reason != v1alpha1.CSVReasonInterOperatorGroupOwnerConflict { + logger.WithField("apis", providedAPIs).Warn("intersecting operatorgroups provide the same apis") + out.SetPhaseWithEvent(v1alpha1.CSVPhaseFailed, v1alpha1.CSVReasonInterOperatorGroupOwnerConflict, "intersecting operatorgroups provide the same apis", now, a.recorder) + a.cleanupCSVDeployments(logger, out) + } + return + case result == resolver.AddAPIs: + // Add the CSV's provided APIs to its OperatorGroup's annotation + logger.WithField("apis", providedAPIs).Debug("adding csv provided apis to operatorgroup") + union := groupSurface.ProvidedAPIs().Union(providedAPIs) + unionedAnnotations := operatorGroup.GetAnnotations() + if unionedAnnotations == nil { + unionedAnnotations = make(map[string]string) + } + unionedAnnotations[v1alpha2.OperatorGroupProvidedAPIsAnnotationKey] = union.String() + operatorGroup.SetAnnotations(unionedAnnotations) + if _, err := a.client.OperatorsV1alpha2().OperatorGroups(operatorGroup.GetNamespace()).Update(operatorGroup); err != nil && !k8serrors.IsNotFound(err) { + syncError = fmt.Errorf("could not update operatorgroups %s annotation: %v", v1alpha2.OperatorGroupProvidedAPIsAnnotationKey, err) + } + a.csvQueueSet.Requeue(out.GetName(), out.GetNamespace()) + return + case result == resolver.RemoveAPIs: + // Remove the CSV's provided APIs from its OperatorGroup's annotation + logger.WithField("apis", providedAPIs).Debug("removing csv provided apis from operatorgroup") + difference := groupSurface.ProvidedAPIs().Difference(providedAPIs) + if diffedAnnotations := operatorGroup.GetAnnotations(); diffedAnnotations != nil { + diffedAnnotations[v1alpha2.OperatorGroupProvidedAPIsAnnotationKey] = difference.String() + operatorGroup.SetAnnotations(diffedAnnotations) + if _, err := a.client.OperatorsV1alpha2().OperatorGroups(operatorGroup.GetNamespace()).Update(operatorGroup); err != nil && !k8serrors.IsNotFound(err) { + syncError = fmt.Errorf("could not update operatorgroups %s annotation: %v", v1alpha2.OperatorGroupProvidedAPIsAnnotationKey, err) + } + } + a.csvQueueSet.Requeue(out.GetName(), out.GetNamespace()) + return + default: + logger.WithField("apis", providedAPIs).Debug("no intersecting operatorgroups provide the same apis") + } + switch out.Status.Phase { case v1alpha1.CSVPhaseNone: logger.Info("scheduling ClusterServiceVersion for requirement verification") @@ -678,9 +816,25 @@ func (a *Operator) transitionCSVState(in v1alpha1.ClusterServiceVersion) (out *v out.Status.Reason == v1alpha1.CSVReasonNoOperatorGroup || out.Status.Reason == v1alpha1.CSVReasonTooManyOperatorGroups || out.Status.Reason == v1alpha1.CSVReasonUnsupportedOperatorGroup { - logger.Info("target namespaces supported. Transitioning to Pending...") + logger.Info("InstallModes now support target namespaces. Transitioning to Pending...") // Check occurred before switch, safe to transition to pending - out.SetPhaseWithEvent(v1alpha1.CSVPhasePending, v1alpha1.CSVReasonRequirementsUnknown, "InstallModes now support target namespaces. Transitioning to Pending.", now, a.recorder) + out.SetPhaseWithEvent(v1alpha1.CSVPhasePending, v1alpha1.CSVReasonRequirementsUnknown, "InstallModes now support target namespaces", now, a.recorder) + return + } + + // Check if failed due to conflicting OperatorGroups + if out.Status.Reason == v1alpha1.CSVReasonInterOperatorGroupOwnerConflict { + logger.Info("OperatorGroup no longer intersecting with conflicting owner. Transitioning to Pending...") + // Check occurred before switch, safe to transition to pending + out.SetPhaseWithEvent(v1alpha1.CSVPhasePending, v1alpha1.CSVReasonRequirementsUnknown, "OperatorGroup no longer intersecting with conflicting owner", now, a.recorder) + return + } + + // Check if failed due to an attempt to modify a static OperatorGroup + if out.Status.Reason == v1alpha1.CSVReasonCannotModifyStaticOperatorGroupProvidedAPIs { + logger.Info("static OperatorGroup and intersecting groups now support providedAPIs...") + // Check occurred before switch, safe to transition to pending + out.SetPhaseWithEvent(v1alpha1.CSVPhasePending, v1alpha1.CSVReasonRequirementsUnknown, "static OperatorGroup and intersecting groups now support providedAPIs", now, a.recorder) return } @@ -966,7 +1120,7 @@ func (a *Operator) handleDeletion(obj interface{}) { ownee, ok = tombstone.Obj.(metav1.Object) if !ok { - utilruntime.HandleError(fmt.Errorf("Tombstone contained object that is not a Namespace %#v", obj)) + utilruntime.HandleError(fmt.Errorf("Tombstone contained object that is not a metav1.Object %#v", obj)) return } } @@ -1015,8 +1169,7 @@ func (a *Operator) requeueOwnerCSVs(ownee metav1.Object) { for _, csv := range csvs { csvSet[csv.GetUID()] = csv } - - logger.Infof("CSV set: %+v", csvSet) + logger.WithField("clusterwide", len(csvs)).Debug("number of csvs") // Requeue existing owner CSVs for _, owner := range owners { @@ -1032,3 +1185,28 @@ func (a *Operator) requeueOwnerCSVs(ownee metav1.Object) { } } } + +func (a *Operator) cleanupCSVDeployments(logger *logrus.Entry, csv *v1alpha1.ClusterServiceVersion) { + // Extract the InstallStrategy for the deployment + strategy, err := a.resolver.UnmarshalStrategy(csv.Spec.InstallStrategy) + if err != nil { + logger.Warn("could not parse install strategy while cleaning up CSV deployment") + return + } + + // Assume the strategy is for a deployment + strategyDetailsDeployment, ok := strategy.(*install.StrategyDetailsDeployment) + if !ok { + logger.Warnf("could not cast install strategy as type %T", strategyDetailsDeployment) + return + } + + // Delete deployments + for _, spec := range strategyDetailsDeployment.DeploymentSpecs { + logger := logger.WithField("deployment", spec.Name) + logger.Debug("cleaning up CSV deployment") + if err := a.OpClient.DeleteDeployment(csv.GetNamespace(), spec.Name, &metav1.DeleteOptions{}); err != nil { + logger.WithField("err", err).Warn("error cleaning up CSV deployment") + } + } +} \ No newline at end of file diff --git a/pkg/controller/operators/olm/operator_test.go b/pkg/controller/operators/olm/operator_test.go index 2dc6596fad..cc796a23c6 100644 --- a/pkg/controller/operators/olm/operator_test.go +++ b/pkg/controller/operators/olm/operator_test.go @@ -35,6 +35,7 @@ import ( apiregistrationfake "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/fake" kagg "k8s.io/kube-aggregator/pkg/client/informers/externalversions" + "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver" "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1" "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha2" "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned" @@ -47,6 +48,8 @@ import ( "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorlister" "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/ownerutil" "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/queueinformer" + "github.com/operator-framework/operator-lifecycle-manager/pkg/fakes" + ) // Fakes @@ -119,7 +122,7 @@ func apiResourcesForObjects(objs []runtime.Object) []*metav1.APIResourceList { } // NewFakeOperator creates a new operator using fake clients -func NewFakeOperator(clientObjs []runtime.Object, k8sObjs []runtime.Object, extObjs []runtime.Object, regObjs []runtime.Object, resolver install.StrategyResolverInterface, namespaces []string, stopCh <-chan struct{}) (*Operator, []cache.InformerSynced, error) { +func NewFakeOperator(clientObjs []runtime.Object, k8sObjs []runtime.Object, extObjs []runtime.Object, regObjs []runtime.Object, strategyResolver install.StrategyResolverInterface, reconciler resolver.APIIntersectionReconciler, namespaces []string, stopCh <-chan struct{}) (*Operator, []cache.InformerSynced, error) { // Create client fakes clientFake := fake.NewSimpleClientset(clientObjs...) k8sClientFake := k8sfake.NewSimpleClientset(k8sObjs...) @@ -131,13 +134,19 @@ func NewFakeOperator(clientObjs []runtime.Object, k8sObjs []runtime.Object, extO return nil, nil, err } + if reconciler == nil { + // Use the default reconciler if one isn't given + reconciler = resolver.APIIntersectionReconcileFunc(resolver.ReconcileAPIIntersection) + } + // Create the new operator queueOperator, err := queueinformer.NewOperatorFromClient(opClientFake, logrus.StandardLogger()) op := &Operator{ Operator: queueOperator, client: clientFake, + resolver: strategyResolver, + apiReconciler: reconciler, lister: operatorlister.NewLister(), - resolver: resolver, csvQueueSet: make(map[string]workqueue.RateLimitingInterface), recorder: eventRecorder, } @@ -210,6 +219,12 @@ func (o *Operator) GetClient() versioned.Interface { return o.client } +func buildFakeAPIIntersectionReconcilerThatReturns(result resolver.APIReconciliationResult) *fakes.FakeAPIIntersectionReconciler { + reconciler := &fakes.FakeAPIIntersectionReconciler{} + reconciler.ReconcileReturns(result) + return reconciler +} + // Tests func deployment(deploymentName, namespace, serviceAccountName string, templateAnnotations map[string]string) *appsv1.Deployment { @@ -407,6 +422,21 @@ func addAnnotations(annotations map[string]string, add map[string]string) map[st return out } +func addAnnotation(obj runtime.Object, key string, value string) runtime.Object { + meta, ok := obj.(metav1.Object) + if !ok { + panic("could not find metadata on object") + } + return withAnnotations(obj, addAnnotations(meta.GetAnnotations(), map[string]string{key: value})) +} + +func csvWithStatusReason(csv *v1alpha1.ClusterServiceVersion, reason v1alpha1.ConditionReason) *v1alpha1.ClusterServiceVersion { + out := csv.DeepCopy() + out.Status.Reason = reason + return csv +} + + func installStrategy(deploymentName string, permissions []install.StrategyDeploymentPermissions, clusterPermissions []install.StrategyDeploymentPermissions) v1alpha1.NamedInstallStrategy { var singleInstance = int32(1) strategy := install.StrategyDetailsDeployment{ @@ -588,13 +618,13 @@ func apiService(group, version, serviceName, serviceNamespace, deploymentName st return apiService } -func crd(name string, version string) *v1beta1.CustomResourceDefinition { +func crd(name, version, group string) *v1beta1.CustomResourceDefinition { return &v1beta1.CustomResourceDefinition{ ObjectMeta: metav1.ObjectMeta{ - Name: name + "group", + Name: name + "." + group, }, Spec: v1beta1.CustomResourceDefinitionSpec{ - Group: name + "group", + Group: group, Versions: []v1beta1.CustomResourceDefinitionVersion{ { Name: version, @@ -709,6 +739,9 @@ func TestTransitionCSV(t *testing.T) { phase v1alpha1.ClusterServiceVersionPhase reason v1alpha1.ConditionReason } + type operatorConfig struct{ + reconciler resolver.APIIntersectionReconciler + } type initial struct { csvs []runtime.Object clientObjs []runtime.Object @@ -722,6 +755,7 @@ func TestTransitionCSV(t *testing.T) { } tests := []struct { name string + config operatorConfig initial initial expected expected }{ @@ -734,12 +768,12 @@ func TestTransitionCSV(t *testing.T) { "0.0.0", "", installStrategy("csv1-dep1", nil, nil), - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhaseNone, ), defaultTemplateAnnotations), }, - clientObjs: []runtime.Object{defaultOperatorGroup}, + clientObjs: []runtime.Object{addAnnotation(defaultOperatorGroup, v1alpha2.OperatorGroupProvidedAPIsAnnotationKey, "c1.v1.g1")}, }, expected: expected{ csvStates: map[string]csvState{ @@ -761,7 +795,7 @@ func TestTransitionCSV(t *testing.T) { v1alpha1.CSVPhaseNone, ), defaultTemplateAnnotations), nil, apis("a1.corev1.a1Kind")), }, - clientObjs: []runtime.Object{defaultOperatorGroup}, + clientObjs: []runtime.Object{addAnnotation(defaultOperatorGroup, v1alpha2.OperatorGroupProvidedAPIsAnnotationKey, "a1Kind.corev1.a1")}, }, expected: expected{ csvStates: map[string]csvState{ @@ -778,14 +812,14 @@ func TestTransitionCSV(t *testing.T) { "0.0.0", "", v1alpha1.NamedInstallStrategy{"deployment", json.RawMessage{}}, - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhasePending, ), defaultTemplateAnnotations), }, - clientObjs: []runtime.Object{defaultOperatorGroup}, + clientObjs: []runtime.Object{addAnnotation(defaultOperatorGroup, v1alpha2.OperatorGroupProvidedAPIsAnnotationKey, "c1.v1.g1")}, crds: []runtime.Object{ - crd("c1", "v1"), + crd("c1", "v1", "g1"), }, }, expected: expected{ @@ -816,14 +850,14 @@ func TestTransitionCSV(t *testing.T) { }, }, }), - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhasePending, ), defaultTemplateAnnotations), }, - clientObjs: []runtime.Object{defaultOperatorGroup}, + clientObjs: []runtime.Object{addAnnotation(defaultOperatorGroup, v1alpha2.OperatorGroupProvidedAPIsAnnotationKey, "c1.v1.g1")}, crds: []runtime.Object{ - crd("c1", "v1"), + crd("c1", "v1", "g1"), }, objs: []runtime.Object{ &corev1.ServiceAccount{ @@ -849,12 +883,12 @@ func TestTransitionCSV(t *testing.T) { "0.0.0", "", installStrategy("csv1-dep1", nil, nil), - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhasePending, ), defaultTemplateAnnotations), }, - clientObjs: []runtime.Object{defaultOperatorGroup}, + clientObjs: []runtime.Object{addAnnotation(defaultOperatorGroup, v1alpha2.OperatorGroupProvidedAPIsAnnotationKey, "c1.v1.g1")}, crds: []runtime.Object{}, }, expected: expected{ @@ -944,7 +978,7 @@ func TestTransitionCSV(t *testing.T) { }, }, { - name: "SingleCSVPendingToFailed/APIService/Owned/DeploymentNotFound", + name: "SingleCSVPendingToPending/APIService/Owned/DeploymentNotFound", initial: initial{ csvs: []runtime.Object{ withAPIServices(csvWithAnnotations(csv("csv1", @@ -952,14 +986,14 @@ func TestTransitionCSV(t *testing.T) { "0.0.0", "", installStrategy("b1", nil, nil), - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhasePending, ), defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil), }, - clientObjs: []runtime.Object{defaultOperatorGroup}, + clientObjs: []runtime.Object{addAnnotation(defaultOperatorGroup, v1alpha2.OperatorGroupProvidedAPIsAnnotationKey, "c1.v1.g1,a1Kind.v1.a1")}, crds: []runtime.Object{ - crd("c1", "v1"), + crd("c1", "v1", "g1"), }, }, expected: expected{ @@ -980,7 +1014,7 @@ func TestTransitionCSV(t *testing.T) { "0.0.0", "", installStrategy("csv1-dep1", nil, nil), - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhaseSucceeded, ), defaultTemplateAnnotations), @@ -989,14 +1023,14 @@ func TestTransitionCSV(t *testing.T) { "0.0.0", "", installStrategy("csv2-dep1", nil, nil), - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhasePending, ), defaultTemplateAnnotations), }, - clientObjs: []runtime.Object{defaultOperatorGroup}, + clientObjs: []runtime.Object{addAnnotation(defaultOperatorGroup, v1alpha2.OperatorGroupProvidedAPIsAnnotationKey, "c1.v1.g1")}, crds: []runtime.Object{ - crd("c1", "v1"), + crd("c1", "v1", "g1"), }, objs: []runtime.Object{ deployment("csv1-dep1", namespace, "sa", defaultTemplateAnnotations), @@ -1024,8 +1058,9 @@ func TestTransitionCSV(t *testing.T) { []*v1beta1.CustomResourceDefinition{}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhaseSucceeded, - ), defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil), metav1.NewTime(time.Now().Add(24*time.Hour)), metav1.NewTime(time.Now())), - withAPIServices(csvWithAnnotations(csv("csv2", + ), defaultTemplateAnnotations), + apis("a1.v1.a1Kind"), nil), metav1.NewTime(time.Now().Add(24*time.Hour)), metav1.NewTime(time.Now())), + withAPIServices(csvWithAnnotations(csv("csv2", namespace, "0.0.0", "", @@ -1033,9 +1068,10 @@ func TestTransitionCSV(t *testing.T) { []*v1beta1.CustomResourceDefinition{}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhasePending, - ), defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil), + ), defaultTemplateAnnotations), + apis("a1.v1.a1Kind"), nil), }, - clientObjs: []runtime.Object{defaultOperatorGroup}, + clientObjs: []runtime.Object{addAnnotation(defaultOperatorGroup, v1alpha2.OperatorGroupProvidedAPIsAnnotationKey, "a1Kind.v1.a1")}, apis: []runtime.Object{apiService("a1", "v1", "v1-a1", namespace, "", validCAPEM, apiregistrationv1.ConditionTrue)}, objs: []runtime.Object{ deployment("a1", namespace, "sa", addAnnotations(defaultTemplateAnnotations, map[string]string{ @@ -1098,14 +1134,14 @@ func TestTransitionCSV(t *testing.T) { "0.0.0", "", installStrategy("a1", nil, nil), - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhaseFailed, ), defaultTemplateAnnotations), }, - clientObjs: []runtime.Object{defaultOperatorGroup}, + clientObjs: []runtime.Object{addAnnotation(defaultOperatorGroup, v1alpha2.OperatorGroupProvidedAPIsAnnotationKey, "c1.v1.g1")}, crds: []runtime.Object{ - crd("c1", "v1"), + crd("c1", "v1", "g1"), }, }, expected: expected{ @@ -1123,12 +1159,12 @@ func TestTransitionCSV(t *testing.T) { "0.0.0", "", installStrategy("a1", nil, nil), - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhaseFailed, ), defaultTemplateAnnotations), }, - clientObjs: []runtime.Object{defaultOperatorGroup}, + clientObjs: []runtime.Object{addAnnotation(defaultOperatorGroup, v1alpha2.OperatorGroupProvidedAPIsAnnotationKey, "c1.v1.g1")}, objs: []runtime.Object{ deployment("a1", namespace, "sa", defaultTemplateAnnotations), }, @@ -1148,12 +1184,12 @@ func TestTransitionCSV(t *testing.T) { "0.0.0", "", v1alpha1.NamedInstallStrategy{"deployment", json.RawMessage{}}, - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhaseFailed, ), defaultTemplateAnnotations), }, - clientObjs: []runtime.Object{defaultOperatorGroup}, + clientObjs: []runtime.Object{addAnnotation(defaultOperatorGroup, v1alpha2.OperatorGroupProvidedAPIsAnnotationKey, "c1.v1.g1")}, objs: []runtime.Object{ deployment("a1", namespace, "sa", defaultTemplateAnnotations), }, @@ -1173,14 +1209,14 @@ func TestTransitionCSV(t *testing.T) { "0.0.0", "", installStrategy("csv1-dep1", nil, nil), - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhasePending, ), defaultTemplateAnnotations), }, - clientObjs: []runtime.Object{defaultOperatorGroup}, + clientObjs: []runtime.Object{addAnnotation(defaultOperatorGroup, v1alpha2.OperatorGroupProvidedAPIsAnnotationKey, "c1.v1.g1")}, crds: []runtime.Object{ - crd("c1", "v1"), + crd("c1", "v1", "g1"), }, }, expected: expected{ @@ -1221,14 +1257,14 @@ func TestTransitionCSV(t *testing.T) { "0.0.0", "", installStrategy("csv1-dep1", nil, nil), - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhaseInstallReady, ), defaultTemplateAnnotations), }, - clientObjs: []runtime.Object{defaultOperatorGroup}, + clientObjs: []runtime.Object{addAnnotation(defaultOperatorGroup, v1alpha2.OperatorGroupProvidedAPIsAnnotationKey, "c1.v1.g1")}, crds: []runtime.Object{ - crd("c1", "v1"), + crd("c1", "v1", "g1"), }, }, expected: expected{ @@ -1246,14 +1282,14 @@ func TestTransitionCSV(t *testing.T) { "0.0.0", "", installStrategy("a1", nil, nil), - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhaseInstallReady, - ), defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil), + ),defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil), }, - clientObjs: []runtime.Object{defaultOperatorGroup}, + clientObjs: []runtime.Object{addAnnotation(defaultOperatorGroup, v1alpha2.OperatorGroupProvidedAPIsAnnotationKey, "c1.v1.g1,a1Kind.v1.a1")}, crds: []runtime.Object{ - crd("c1", "v1"), + crd("c1", "v1", "g1"), }, }, expected: expected{ @@ -1271,10 +1307,10 @@ func TestTransitionCSV(t *testing.T) { "0.0.0", "", installStrategy("a1", nil, nil), - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhaseSucceeded, - ), defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil), metav1.Now(), metav1.Now()), + ),defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil), metav1.Now(), metav1.Now()), }, clientObjs: []runtime.Object{defaultOperatorGroup}, apis: []runtime.Object{ @@ -1322,7 +1358,7 @@ func TestTransitionCSV(t *testing.T) { clusterRoleBinding("v1.a1-system:auth-delegator", "system:auth-delegator", "sa", namespace), }, crds: []runtime.Object{ - crd("c1", "v1"), + crd("c1", "v1", "g1"), }, }, expected: expected{ @@ -1340,10 +1376,10 @@ func TestTransitionCSV(t *testing.T) { "0.0.0", "", installStrategy("a1", nil, nil), - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhaseSucceeded, - ), defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil), metav1.Now(), metav1.Now()), + ),defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil), metav1.Now(), metav1.Now()), }, clientObjs: []runtime.Object{defaultOperatorGroup}, apis: []runtime.Object{ @@ -1391,7 +1427,7 @@ func TestTransitionCSV(t *testing.T) { clusterRoleBinding("v1.a1-system:auth-delegator", "system:auth-delegator", "sa", namespace), }, crds: []runtime.Object{ - crd("c1", "v1"), + crd("c1", "v1", "g1"), }, }, expected: expected{ @@ -1409,10 +1445,10 @@ func TestTransitionCSV(t *testing.T) { "0.0.0", "", installStrategy("a1", nil, nil), - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhaseSucceeded, - ), defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil), metav1.Now(), metav1.Now()), + ),defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil), metav1.Now(), metav1.Now()), }, clientObjs: []runtime.Object{defaultOperatorGroup}, apis: []runtime.Object{ @@ -1460,7 +1496,7 @@ func TestTransitionCSV(t *testing.T) { clusterRoleBinding("v1.a1-system:auth-delegator", "system:auth-delegator", "sa", namespace), }, crds: []runtime.Object{ - crd("c1", "v1"), + crd("c1", "v1", "g1"), }, }, expected: expected{ @@ -1478,10 +1514,10 @@ func TestTransitionCSV(t *testing.T) { "0.0.0", "", installStrategy("a1", nil, nil), - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhaseSucceeded, - ), defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil), metav1.Now(), metav1.Now()), + ),defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil), metav1.Now(), metav1.Now()), }, clientObjs: []runtime.Object{defaultOperatorGroup}, apis: []runtime.Object{ @@ -1529,7 +1565,7 @@ func TestTransitionCSV(t *testing.T) { clusterRoleBinding("v1.a1-system:auth-delegator", "system:auth-delegator", "sa", namespace), }, crds: []runtime.Object{ - crd("c1", "v1"), + crd("c1", "v1", "g1"), }, }, expected: expected{ @@ -1547,10 +1583,10 @@ func TestTransitionCSV(t *testing.T) { "0.0.0", "", installStrategy("a1", nil, nil), - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhaseSucceeded, - ), defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil), metav1.Now(), metav1.Now()), + ),defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil), metav1.Now(), metav1.Now()), }, clientObjs: []runtime.Object{defaultOperatorGroup}, apis: []runtime.Object{ @@ -1598,7 +1634,7 @@ func TestTransitionCSV(t *testing.T) { clusterRoleBinding("v1.a1-system:auth-delegator", "system:auth-delegator", "sa", namespace), }, crds: []runtime.Object{ - crd("c1", "v1"), + crd("c1", "v1", "g1"), }, }, expected: expected{ @@ -1616,10 +1652,10 @@ func TestTransitionCSV(t *testing.T) { "0.0.0", "", installStrategy("a1", nil, nil), - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhaseSucceeded, - ), defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil), metav1.Now(), metav1.Now()), + ),defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil), metav1.Now(), metav1.Now()), }, clientObjs: []runtime.Object{defaultOperatorGroup}, apis: []runtime.Object{ @@ -1667,7 +1703,7 @@ func TestTransitionCSV(t *testing.T) { clusterRoleBinding("v1.a1-system:auth-delegator", "system:auth-delegator", "sa", namespace), }, crds: []runtime.Object{ - crd("c1", "v1"), + crd("c1", "v1", "g1"), }, }, expected: expected{ @@ -1685,10 +1721,10 @@ func TestTransitionCSV(t *testing.T) { "0.0.0", "", installStrategy("a1", nil, nil), - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhaseSucceeded, - ), defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil), metav1.Now(), metav1.Now()), + ),defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil), metav1.Now(), metav1.Now()), }, clientObjs: []runtime.Object{defaultOperatorGroup}, apis: []runtime.Object{ @@ -1736,7 +1772,7 @@ func TestTransitionCSV(t *testing.T) { clusterRoleBinding("v1.a1-system:auth-delegator", "system:auth-delegator", "sa", namespace), }, crds: []runtime.Object{ - crd("c1", "v1"), + crd("c1", "v1", "g1"), }, }, expected: expected{ @@ -1754,10 +1790,10 @@ func TestTransitionCSV(t *testing.T) { "0.0.0", "", installStrategy("a1", nil, nil), - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhaseFailed, - ), defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil), metav1.Now(), metav1.Now()), + ),defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil), metav1.Now(), metav1.Now()), }, clientObjs: []runtime.Object{defaultOperatorGroup}, apis: []runtime.Object{ @@ -1805,7 +1841,7 @@ func TestTransitionCSV(t *testing.T) { clusterRoleBinding("v1.a1-system:auth-delegator", "system:auth-delegator", "sa", namespace), }, crds: []runtime.Object{ - crd("c1", "v1"), + crd("c1", "v1", "g1"), }, }, expected: expected{ @@ -1823,7 +1859,7 @@ func TestTransitionCSV(t *testing.T) { "0.0.0", "", installStrategy("a1", nil, nil), - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhaseFailed, ), defaultTemplateAnnotations), v1alpha1.CSVReasonUnsupportedOperatorGroup), @@ -1835,7 +1871,7 @@ func TestTransitionCSV(t *testing.T) { serviceAccount("sa", namespace), }, crds: []runtime.Object{ - crd("c1", "v1"), + crd("c1", "v1", "g1"), }, }, expected: expected{ @@ -1853,7 +1889,7 @@ func TestTransitionCSV(t *testing.T) { "0.0.0", "", installStrategy("a1", nil, nil), - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhaseFailed, ), defaultTemplateAnnotations), v1alpha1.CSVReasonNoOperatorGroup), @@ -1865,7 +1901,7 @@ func TestTransitionCSV(t *testing.T) { serviceAccount("sa", namespace), }, crds: []runtime.Object{ - crd("c1", "v1"), + crd("c1", "v1", "g1"), }, }, expected: expected{ @@ -1883,7 +1919,7 @@ func TestTransitionCSV(t *testing.T) { "0.0.0", "", installStrategy("a1", nil, nil), - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhaseFailed, ), defaultTemplateAnnotations), v1alpha1.CSVReasonTooManyOperatorGroups), @@ -1895,7 +1931,7 @@ func TestTransitionCSV(t *testing.T) { serviceAccount("sa", namespace), }, crds: []runtime.Object{ - crd("c1", "v1"), + crd("c1", "v1", "g1"), }, }, expected: expected{ @@ -1913,7 +1949,7 @@ func TestTransitionCSV(t *testing.T) { "0.0.0", "", installStrategy("a1", nil, nil), - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhaseSucceeded, ), defaultTemplateAnnotations), v1alpha1.CSVReasonInstallSuccessful), @@ -1932,7 +1968,7 @@ func TestTransitionCSV(t *testing.T) { serviceAccount("sa", namespace), }, crds: []runtime.Object{ - crd("c1", "v1"), + crd("c1", "v1", "g1"), }, }, expected: expected{ @@ -1950,7 +1986,7 @@ func TestTransitionCSV(t *testing.T) { "0.0.0", "", installStrategy("a1", nil, nil), - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhaseSucceeded, ), defaultTemplateAnnotations), v1alpha1.CSVReasonInstallSuccessful), @@ -1961,7 +1997,7 @@ func TestTransitionCSV(t *testing.T) { serviceAccount("sa", namespace), }, crds: []runtime.Object{ - crd("c1", "v1"), + crd("c1", "v1", "g1"), }, }, expected: expected{ @@ -1982,7 +2018,7 @@ func TestTransitionCSV(t *testing.T) { "0.0.0", "", installStrategy("a1", nil, nil), - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhaseSucceeded, ), defaultTemplateAnnotations), v1alpha1.CSVReasonInstallSuccessful), @@ -2010,7 +2046,7 @@ func TestTransitionCSV(t *testing.T) { serviceAccount("sa", namespace), }, crds: []runtime.Object{ - crd("c1", "v1"), + crd("c1", "v1", "g1"), }, }, expected: expected{ @@ -2031,14 +2067,14 @@ func TestTransitionCSV(t *testing.T) { "0.0.0", "", v1alpha1.NamedInstallStrategy{"deployment", json.RawMessage{}}, - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhaseInstallReady, ), defaultTemplateAnnotations), }, clientObjs: []runtime.Object{defaultOperatorGroup}, crds: []runtime.Object{ - crd("c1", "v1"), + crd("c1", "v1", "g1"), }, }, expected: expected{ @@ -2056,14 +2092,14 @@ func TestTransitionCSV(t *testing.T) { "0.0.0", "", installStrategy("csv1-dep1", nil, nil), - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhaseInstalling, ), defaultTemplateAnnotations), }, clientObjs: []runtime.Object{defaultOperatorGroup}, crds: []runtime.Object{ - crd("c1", "v1"), + crd("c1", "v1", "g1"), }, objs: []runtime.Object{ deployment("csv1-dep1", namespace, "sa", defaultTemplateAnnotations), @@ -2084,7 +2120,7 @@ func TestTransitionCSV(t *testing.T) { "0.0.0", "", installStrategy("a1", nil, nil), - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhaseSucceeded, ), defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil), @@ -2106,7 +2142,7 @@ func TestTransitionCSV(t *testing.T) { "0.0.0", "", installStrategy("csv1-dep1", nil, nil), - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhaseSucceeded, ), defaultTemplateAnnotations), @@ -2115,14 +2151,14 @@ func TestTransitionCSV(t *testing.T) { "0.0.0", "csv1", installStrategy("csv2-dep1", nil, nil), - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhaseNone, ), defaultTemplateAnnotations), }, clientObjs: []runtime.Object{defaultOperatorGroup}, crds: []runtime.Object{ - crd("c1", "v1"), + crd("c1", "v1", "g1"), }, objs: []runtime.Object{ deployment("csv1-dep1", namespace, "sa", defaultTemplateAnnotations), @@ -2144,7 +2180,7 @@ func TestTransitionCSV(t *testing.T) { "0.0.0", "", installStrategy("csv1-dep1", nil, nil), - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhaseReplacing, ), defaultTemplateAnnotations), @@ -2153,14 +2189,14 @@ func TestTransitionCSV(t *testing.T) { "0.0.0", "csv1", installStrategy("csv2-dep1", nil, nil), - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhaseSucceeded, ), defaultTemplateAnnotations), }, clientObjs: []runtime.Object{defaultOperatorGroup}, crds: []runtime.Object{ - crd("c1", "v1"), + crd("c1", "v1", "g1"), }, objs: []runtime.Object{ deployment("csv1-dep1", namespace, "sa", defaultTemplateAnnotations), @@ -2183,7 +2219,7 @@ func TestTransitionCSV(t *testing.T) { "0.0.0", "", installStrategy("csv1-dep1", nil, nil), - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhaseDeleting, ), defaultTemplateAnnotations), @@ -2192,14 +2228,14 @@ func TestTransitionCSV(t *testing.T) { "0.0.0", "csv1", installStrategy("csv2-dep1", nil, nil), - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhaseSucceeded, ), defaultTemplateAnnotations), }, clientObjs: []runtime.Object{defaultOperatorGroup}, crds: []runtime.Object{ - crd("c1", "v1"), + crd("c1", "v1", "g1"), }, objs: []runtime.Object{ deployment("csv1-dep1", namespace, "sa", defaultTemplateAnnotations), @@ -2223,7 +2259,7 @@ func TestTransitionCSV(t *testing.T) { "0.0.0", "csv2", installStrategy("csv3-dep1", nil, nil), - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhaseSucceeded, ), defaultTemplateAnnotations), @@ -2232,7 +2268,7 @@ func TestTransitionCSV(t *testing.T) { "0.0.0", "", installStrategy("csv1-dep1", nil, nil), - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhaseReplacing, ), defaultTemplateAnnotations), @@ -2241,14 +2277,14 @@ func TestTransitionCSV(t *testing.T) { "0.0.0", "csv1", installStrategy("csv2-dep1", nil, nil), - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhaseReplacing, ), defaultTemplateAnnotations), }, clientObjs: []runtime.Object{defaultOperatorGroup}, crds: []runtime.Object{ - crd("c1", "v1"), + crd("c1", "v1", "g1"), }, objs: []runtime.Object{ deployment("csv1-dep1", namespace, "sa", defaultTemplateAnnotations), @@ -2273,7 +2309,7 @@ func TestTransitionCSV(t *testing.T) { "0.0.0", "csv2", installStrategy("csv3-dep1", nil, nil), - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhaseSucceeded, ), defaultTemplateAnnotations), @@ -2282,7 +2318,7 @@ func TestTransitionCSV(t *testing.T) { "0.0.0", "", installStrategy("csv1-dep1", nil, nil), - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhaseDeleting, ), defaultTemplateAnnotations), @@ -2291,14 +2327,14 @@ func TestTransitionCSV(t *testing.T) { "0.0.0", "csv1", installStrategy("csv2-dep1", nil, nil), - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhaseReplacing, ), defaultTemplateAnnotations), }, clientObjs: []runtime.Object{defaultOperatorGroup}, crds: []runtime.Object{ - crd("c1", "v1"), + crd("c1", "v1", "g1"), }, objs: []runtime.Object{ deployment("csv1-dep1", namespace, "sa", defaultTemplateAnnotations), @@ -2323,7 +2359,7 @@ func TestTransitionCSV(t *testing.T) { "0.0.0", "csv1", installStrategy("csv2-dep1", nil, nil), - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhaseReplacing, ), defaultTemplateAnnotations), @@ -2332,14 +2368,14 @@ func TestTransitionCSV(t *testing.T) { "0.0.0", "csv2", installStrategy("csv3-dep1", nil, nil), - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhaseSucceeded, ), defaultTemplateAnnotations), }, clientObjs: []runtime.Object{defaultOperatorGroup}, crds: []runtime.Object{ - crd("c1", "v1"), + crd("c1", "v1", "g1"), }, objs: []runtime.Object{ deployment("csv2-dep1", namespace, "sa", defaultTemplateAnnotations), @@ -2363,7 +2399,7 @@ func TestTransitionCSV(t *testing.T) { "0.0.0", "csv1", installStrategy("csv2-dep1", nil, nil), - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhaseDeleting, ), defaultTemplateAnnotations), @@ -2372,14 +2408,14 @@ func TestTransitionCSV(t *testing.T) { "0.0.0", "csv2", installStrategy("csv3-dep1", nil, nil), - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhaseSucceeded, ), defaultTemplateAnnotations), }, clientObjs: []runtime.Object{defaultOperatorGroup}, crds: []runtime.Object{ - crd("c1", "v1"), + crd("c1", "v1", "g1"), }, objs: []runtime.Object{ deployment("csv2-dep1", namespace, "sa", defaultTemplateAnnotations), @@ -2393,6 +2429,301 @@ func TestTransitionCSV(t *testing.T) { }, }, }, + { + name: "SingleCSVNoneToFailed/InterOperatorGroupOwnerConflict", + config: operatorConfig{reconciler: buildFakeAPIIntersectionReconcilerThatReturns(resolver.APIConflict)}, + initial: initial{ + csvs: []runtime.Object{ + csvWithAnnotations(csv("csv1", + namespace, + "0.0.0", + "", + installStrategy("csv1-dep1", nil, nil), + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, + []*v1beta1.CustomResourceDefinition{}, + v1alpha1.CSVPhaseNone, + ), defaultTemplateAnnotations), + }, + clientObjs: []runtime.Object{defaultOperatorGroup}, + }, + expected: expected{ + csvStates: map[string]csvState{ + "csv1": {exists: true, phase: v1alpha1.CSVPhaseFailed, reason: v1alpha1.CSVReasonInterOperatorGroupOwnerConflict}, + }, + }, + }, + { + name: "SingleCSVNoneToNone/AddAPIs", + config: operatorConfig{reconciler: buildFakeAPIIntersectionReconcilerThatReturns(resolver.AddAPIs)}, + initial: initial{ + csvs: []runtime.Object{ + csvWithAnnotations(csv("csv1", + namespace, + "0.0.0", + "", + installStrategy("csv1-dep1", nil, nil), + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, + []*v1beta1.CustomResourceDefinition{}, + v1alpha1.CSVPhaseNone, + ), defaultTemplateAnnotations), + }, + clientObjs: []runtime.Object{defaultOperatorGroup}, + }, + expected: expected{ + csvStates: map[string]csvState{ + "csv1": {exists: true, phase: v1alpha1.CSVPhaseNone}, + }, + }, + }, + { + name: "SingleCSVNoneToNone/RemoveAPIs", + config: operatorConfig{reconciler: buildFakeAPIIntersectionReconcilerThatReturns(resolver.RemoveAPIs)}, + initial: initial{ + csvs: []runtime.Object{ + csvWithAnnotations(csv("csv1", + namespace, + "0.0.0", + "", + installStrategy("csv1-dep1", nil, nil), + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, + []*v1beta1.CustomResourceDefinition{}, + v1alpha1.CSVPhaseNone, + ), defaultTemplateAnnotations), + }, + clientObjs: []runtime.Object{defaultOperatorGroup}, + }, + expected: expected{ + csvStates: map[string]csvState{ + "csv1": {exists: true, phase: v1alpha1.CSVPhaseNone}, + }, + }, + }, + { + name: "SingleCSVNoneToFailed/StaticOperatorGroup/AddAPIs", + config: operatorConfig{reconciler: buildFakeAPIIntersectionReconcilerThatReturns(resolver.AddAPIs)}, + initial: initial{ + csvs: []runtime.Object{ + csvWithAnnotations(csv("csv1", + namespace, + "0.0.0", + "", + installStrategy("csv1-dep1", nil, nil), + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, + []*v1beta1.CustomResourceDefinition{}, + v1alpha1.CSVPhaseNone, + ), defaultTemplateAnnotations), + }, + clientObjs: []runtime.Object{ + func() *v1alpha2.OperatorGroup{ + // Make the default OperatorGroup static + static := defaultOperatorGroup.DeepCopy() + static.Spec.StaticProvidedAPIs = true + return static + }(), + }, + }, + expected: expected{ + csvStates: map[string]csvState{ + "csv1": {exists: true, phase: v1alpha1.CSVPhaseFailed, reason: v1alpha1.CSVReasonCannotModifyStaticOperatorGroupProvidedAPIs}, + }, + }, + }, + { + name: "SingleCSVNoneToFailed/StaticOperatorGroup/RemoveAPIs", + config: operatorConfig{reconciler: buildFakeAPIIntersectionReconcilerThatReturns(resolver.RemoveAPIs)}, + initial: initial{ + csvs: []runtime.Object{ + csvWithAnnotations(csv("csv1", + namespace, + "0.0.0", + "", + installStrategy("csv1-dep1", nil, nil), + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, + []*v1beta1.CustomResourceDefinition{}, + v1alpha1.CSVPhaseNone, + ), defaultTemplateAnnotations), + }, + clientObjs: []runtime.Object{ + func() *v1alpha2.OperatorGroup{ + // Make the default OperatorGroup static + static := defaultOperatorGroup.DeepCopy() + static.Spec.StaticProvidedAPIs = true + return static + }(), + }, + }, + expected: expected{ + csvStates: map[string]csvState{ + "csv1": {exists: true, phase: v1alpha1.CSVPhaseFailed, reason: v1alpha1.CSVReasonCannotModifyStaticOperatorGroupProvidedAPIs}, + }, + }, + }, + { + name: "SingleCSVNoneToPending/StaticOperatorGroup/NoAPIConflict", + config: operatorConfig{reconciler: buildFakeAPIIntersectionReconcilerThatReturns(resolver.NoAPIConflict)}, + initial: initial{ + csvs: []runtime.Object{ + csvWithAnnotations(csv("csv1", + namespace, + "0.0.0", + "", + installStrategy("csv1-dep1", nil, nil), + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, + []*v1beta1.CustomResourceDefinition{}, + v1alpha1.CSVPhaseNone, + ), defaultTemplateAnnotations), + }, + clientObjs: []runtime.Object{ + func() *v1alpha2.OperatorGroup{ + // Make the default OperatorGroup static + static := defaultOperatorGroup.DeepCopy() + static.Spec.StaticProvidedAPIs = true + return static + }(), + }, + }, + expected: expected{ + csvStates: map[string]csvState{ + "csv1": {exists: true, phase: v1alpha1.CSVPhasePending}, + }, + }, + }, + { + name: "SingleCSVFailedToPending/InterOperatorGroupOwnerConflict/NoAPIConflict", + config: operatorConfig{reconciler: buildFakeAPIIntersectionReconcilerThatReturns(resolver.NoAPIConflict)}, + initial: initial{ + csvs: []runtime.Object{ + csvWithAnnotations(csvWithStatusReason(csv("csv1", + namespace, + "0.0.0", + "", + installStrategy("csv1-dep1", nil, nil), + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, + []*v1beta1.CustomResourceDefinition{}, + v1alpha1.CSVPhaseFailed, + ), v1alpha1.CSVReasonInterOperatorGroupOwnerConflict), defaultTemplateAnnotations), + }, + clientObjs: []runtime.Object{defaultOperatorGroup}, + }, + expected: expected{ + csvStates: map[string]csvState{ + "csv1": {exists: true, phase: v1alpha1.CSVPhasePending}, + }, + }, + }, + { + name: "SingleCSVFailedToPending/StaticOperatorGroup/CannotModifyStaticOperatorGroupProvidedAPIs/NoAPIConflict", + config: operatorConfig{reconciler: buildFakeAPIIntersectionReconcilerThatReturns(resolver.NoAPIConflict)}, + initial: initial{ + csvs: []runtime.Object{ + csvWithAnnotations(csvWithStatusReason(csv("csv1", + namespace, + "0.0.0", + "", + installStrategy("csv1-dep1", nil, nil), + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, + []*v1beta1.CustomResourceDefinition{}, + v1alpha1.CSVPhaseFailed, + ), v1alpha1.CSVReasonCannotModifyStaticOperatorGroupProvidedAPIs), defaultTemplateAnnotations), + }, + clientObjs: []runtime.Object{ + func() *v1alpha2.OperatorGroup{ + // Make the default OperatorGroup static + static := defaultOperatorGroup.DeepCopy() + static.Spec.StaticProvidedAPIs = true + return static + }(), + }, + }, + expected: expected{ + csvStates: map[string]csvState{ + "csv1": {exists: true, phase: v1alpha1.CSVPhasePending}, + }, + }, + }, + { + name: "SingleCSVFailedToFailed/InterOperatorGroupOwnerConflict/APIConflict", + config: operatorConfig{reconciler: buildFakeAPIIntersectionReconcilerThatReturns(resolver.APIConflict)}, + initial: initial{ + csvs: []runtime.Object{ + csvWithAnnotations(csvWithStatusReason(csv("csv1", + namespace, + "0.0.0", + "", + installStrategy("csv1-dep1", nil, nil), + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, + []*v1beta1.CustomResourceDefinition{}, + v1alpha1.CSVPhaseFailed, + ), v1alpha1.CSVReasonInterOperatorGroupOwnerConflict), defaultTemplateAnnotations), + }, + clientObjs: []runtime.Object{defaultOperatorGroup}, + }, + expected: expected{ + csvStates: map[string]csvState{ + "csv1": {exists: true, phase: v1alpha1.CSVPhaseFailed, reason: v1alpha1.CSVReasonInterOperatorGroupOwnerConflict}, + }, + }, + }, + { + name: "SingleCSVFailedToFailed/StaticOperatorGroup/CannotModifyStaticOperatorGroupProvidedAPIs/AddAPIs", + config: operatorConfig{reconciler: buildFakeAPIIntersectionReconcilerThatReturns(resolver.AddAPIs)}, + initial: initial{ + csvs: []runtime.Object{ + csvWithAnnotations(csvWithStatusReason(csv("csv1", + namespace, + "0.0.0", + "", + installStrategy("csv1-dep1", nil, nil), + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, + []*v1beta1.CustomResourceDefinition{}, + v1alpha1.CSVPhaseFailed, + ), v1alpha1.CSVReasonCannotModifyStaticOperatorGroupProvidedAPIs), defaultTemplateAnnotations), + }, + clientObjs: []runtime.Object{ + func() *v1alpha2.OperatorGroup{ + // Make the default OperatorGroup static + static := defaultOperatorGroup.DeepCopy() + static.Spec.StaticProvidedAPIs = true + return static + }(), + }, + }, + expected: expected{ + csvStates: map[string]csvState{ + "csv1": {exists: true, phase: v1alpha1.CSVPhaseFailed, reason: v1alpha1.CSVReasonCannotModifyStaticOperatorGroupProvidedAPIs}, + }, + }, + }, + { + name: "SingleCSVFailedToFailed/StaticOperatorGroup/CannotModifyStaticOperatorGroupProvidedAPIs/RemoveAPIs", + config: operatorConfig{reconciler: buildFakeAPIIntersectionReconcilerThatReturns(resolver.RemoveAPIs)}, + initial: initial{ + csvs: []runtime.Object{ + csvWithAnnotations(csvWithStatusReason(csv("csv1", + namespace, + "0.0.0", + "", + installStrategy("csv1-dep1", nil, nil), + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, + []*v1beta1.CustomResourceDefinition{}, + v1alpha1.CSVPhaseFailed, + ), v1alpha1.CSVReasonCannotModifyStaticOperatorGroupProvidedAPIs), defaultTemplateAnnotations), + }, + clientObjs: []runtime.Object{ + func() *v1alpha2.OperatorGroup{ + // Make the default OperatorGroup static + static := defaultOperatorGroup.DeepCopy() + static.Spec.StaticProvidedAPIs = true + return static + }(), + }, + }, + expected: expected{ + csvStates: map[string]csvState{ + "csv1": {exists: true, phase: v1alpha1.CSVPhaseFailed, reason: v1alpha1.CSVReasonCannotModifyStaticOperatorGroupProvidedAPIs}, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -2403,7 +2734,7 @@ func TestTransitionCSV(t *testing.T) { stopCh := make(chan struct{}) defer func() { stopCh <- struct{}{} }() - op, hasSyncedFns, err := NewFakeOperator(clientObjs, tt.initial.objs, tt.initial.crds, tt.initial.apis, &install.StrategyResolver{}, []string{namespace}, stopCh) + op, hasSyncedFns, err := NewFakeOperator(clientObjs, tt.initial.objs, tt.initial.crds, tt.initial.apis, &install.StrategyResolver{}, tt.config.reconciler, []string{namespace}, stopCh) require.NoError(t, err) // run csv sync for each CSV @@ -2463,7 +2794,7 @@ func TestSyncOperatorGroups(t *testing.T) { }, } - crd := crd("c1.fake.api.group", "v1") + crd := crd("c1", "v1", "fake.api.group") operatorCSV := csv("csv1", operatorNamespace, "0.0.0", @@ -2979,6 +3310,177 @@ func TestSyncOperatorGroups(t *testing.T) { }, }}, }, + { + name: "AllNamespaces/CSVPresent/Found/PruneMissingProvidedAPI", + expectedEqual: true, + initial: initial{ + operatorGroup: &v1alpha2.OperatorGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "operator-group-1", + Namespace: operatorNamespace, + Labels: map[string]string{"app": "app-a"}, + Annotations: map[string]string{ + v1alpha2.OperatorGroupProvidedAPIsAnnotationKey: "c1.v1.fake.api.group,missing.v1.fake.api.group", + }, + }, + Spec: v1alpha2.OperatorGroupSpec{}, + }, + clientObjs: []runtime.Object{operatorCSV}, + k8sObjs: []runtime.Object{ + &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: operatorNamespace, + Labels: map[string]string{"app": "app-a"}, + Annotations: map[string]string{"test": "annotation"}, + }, + }, + &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: targetNamespace, + Labels: map[string]string{"app": "app-a"}, + Annotations: map[string]string{"test": "annotation"}, + }, + }, + ownedDeployment, + serviceAccount, + role, + roleBinding, + }, + crds: []runtime.Object{crd}, + }, + expectedStatus: v1alpha2.OperatorGroupStatus{ + Namespaces: []string{corev1.NamespaceAll}, + LastUpdated: timeNow(), + }, + final: final{objects: map[string][]runtime.Object{ + operatorNamespace: { + withAnnotations(operatorCSVFinal.DeepCopy(), map[string]string{v1alpha2.OperatorGroupTargetsAnnotationKey: "", v1alpha2.OperatorGroupAnnotationKey: "operator-group-1", v1alpha2.OperatorGroupNamespaceAnnotationKey: operatorNamespace}), + annotatedGlobalDeployment, + &v1alpha2.OperatorGroup{ + TypeMeta: metav1.TypeMeta{ + Kind: "OperatorGroup", + APIVersion: v1alpha2.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "operator-group-1", + Namespace: operatorNamespace, + Labels: map[string]string{"app": "app-a"}, + Annotations: map[string]string{ + v1alpha2.OperatorGroupProvidedAPIsAnnotationKey: "c1.v1.fake.api.group", + }, + }, + Spec: v1alpha2.OperatorGroupSpec{}, + Status: v1alpha2.OperatorGroupStatus{ + Namespaces: []string{corev1.NamespaceAll}, + LastUpdated: timeNow(), + }, + }, + }, + "": { + &rbacv1.ClusterRole{ + TypeMeta: metav1.TypeMeta{ + Kind: "ClusterRole", + APIVersion: rbacv1.GroupName, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "csv-role", + Labels: map[string]string{ + "olm.owner": "csv1", + "olm.owner.namespace": "operator-ns", + }, + OwnerReferences: []metav1.OwnerReference{ + ownerutil.NonBlockingOwner(targetCSV), + }, + }, + Rules: permissions[0].Rules, + }, + &rbacv1.ClusterRoleBinding{ + TypeMeta: metav1.TypeMeta{ + Kind: "ClusterRoleBinding", + APIVersion: rbacv1.GroupName, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "csv-rolebinding", + Labels: map[string]string{ + "olm.owner": "csv1", + "olm.owner.namespace": "operator-ns", + }, + OwnerReferences: []metav1.OwnerReference{ + ownerutil.NonBlockingOwner(targetCSV), + }, + }, + Subjects: []rbacv1.Subject{ + { + Kind: rbacv1.ServiceAccountKind, + Name: serviceAccount.GetName(), + Namespace: operatorNamespace, + }, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: rbacv1.GroupName, + Kind: "ClusterRole", + Name: "csv-role", + }, + }, + }, + targetNamespace: { + withAnnotations(targetCSV.DeepCopy(), map[string]string{v1alpha2.OperatorGroupAnnotationKey: "operator-group-1", v1alpha2.OperatorGroupNamespaceAnnotationKey: operatorNamespace}), + }, + }}, + }, + { + name: "AllNamespaces/CSVPresent/Found/PruneMissingProvidedAPI/StaticProvidedAPIs", + expectedEqual: true, + initial: initial{ + operatorGroup: &v1alpha2.OperatorGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "operator-group-1", + Namespace: operatorNamespace, + Labels: map[string]string{"app": "app-a"}, + Annotations: map[string]string{ + v1alpha2.OperatorGroupProvidedAPIsAnnotationKey: "missing.fake.api.group", + }, + }, + Spec: v1alpha2.OperatorGroupSpec{ + StaticProvidedAPIs: true, + }, + }, + k8sObjs: []runtime.Object{ + &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: operatorNamespace, + Labels: map[string]string{"app": "app-a"}, + Annotations: map[string]string{"test": "annotation"}, + }, + }, + }, + }, + expectedStatus: v1alpha2.OperatorGroupStatus{ + Namespaces: []string{corev1.NamespaceAll}, + LastUpdated: timeNow(), + }, + final: final{objects: map[string][]runtime.Object{ + operatorNamespace: { + &v1alpha2.OperatorGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "operator-group-1", + Namespace: operatorNamespace, + Labels: map[string]string{"app": "app-a"}, + Annotations: map[string]string{ + v1alpha2.OperatorGroupProvidedAPIsAnnotationKey: "missing.fake.api.group", + }, + }, + Spec: v1alpha2.OperatorGroupSpec{ + StaticProvidedAPIs: true, + }, + Status: v1alpha2.OperatorGroupStatus{ + Namespaces: []string{corev1.NamespaceAll}, + LastUpdated: timeNow(), + }, + }, + }, + }}, + }, { name: "AllNamespaces/CSVPresent/InstallModeNotSupported", expectedEqual: true, @@ -3062,7 +3564,7 @@ func TestSyncOperatorGroups(t *testing.T) { stopCh := make(chan struct{}) defer func() { stopCh <- struct{}{} }() - op, hasSyncedFns, err := NewFakeOperator(tt.initial.clientObjs, tt.initial.k8sObjs, tt.initial.crds, tt.initial.apis, &install.StrategyResolver{}, namespaces, stopCh) + op, hasSyncedFns, err := NewFakeOperator(tt.initial.clientObjs, tt.initial.k8sObjs, tt.initial.crds, tt.initial.apis, &install.StrategyResolver{}, nil, namespaces, stopCh) require.NoError(t, err) err = op.syncOperatorGroups(tt.initial.operatorGroup) @@ -3103,6 +3605,8 @@ func TestSyncOperatorGroups(t *testing.T) { fetched, err = op.OpClient.GetRoleBinding(namespace, o.GetName()) case *v1alpha1.ClusterServiceVersion: fetched, err = op.client.OperatorsV1alpha1().ClusterServiceVersions(namespace).Get(o.GetName(), metav1.GetOptions{}) + case *v1alpha2.OperatorGroup: + fetched, err = op.client.OperatorsV1alpha2().OperatorGroups(namespace).Get(o.GetName(), metav1.GetOptions{}) default: require.Failf(t, "couldn't find expected object", "%#v", object) } @@ -3171,7 +3675,7 @@ func TestIsReplacing(t *testing.T) { // configure cluster state stopCh := make(chan struct{}) defer func() { stopCh <- struct{}{} }() - op, _, err := NewFakeOperator(tt.initial.csvs, nil, nil, nil, &install.StrategyResolver{}, []string{namespace}, stopCh) + op, _, err := NewFakeOperator(tt.initial.csvs, nil, nil, nil, &install.StrategyResolver{}, nil, []string{namespace}, stopCh) require.NoError(t, err) require.Equal(t, tt.expected, op.isReplacing(tt.in)) diff --git a/pkg/controller/operators/olm/operatorgroup.go b/pkg/controller/operators/olm/operatorgroup.go index 835cea34b3..8ff8c49ccc 100644 --- a/pkg/controller/operators/olm/operatorgroup.go +++ b/pkg/controller/operators/olm/operatorgroup.go @@ -11,6 +11,7 @@ import ( k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" + "github.com/sirupsen/logrus" "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1" "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha2" @@ -44,30 +45,102 @@ func (a *Operator) syncOperatorGroups(obj interface{}) error { return fmt.Errorf("casting OperatorGroup failed") } + logger := a.Log.WithFields(logrus.Fields{ + "operatorGroup": op.GetName(), + "namespace": op.GetNamespace(), + }) + targetNamespaces, err := a.updateNamespaceList(op) - a.Log.Debugf("Got targetNamespaces: '%v'", targetNamespaces) if err != nil { - a.Log.Errorf("updateNamespaceList error: %v", err) + logger.WithError(err).Warn("updateNamespaceList error") return err } + logger.WithField("targetNamespaces", targetNamespaces).Debug("updated target namespaces") if err := a.ensureOpGroupClusterRoles(op); err != nil { a.Log.Errorf("ensureOpGroupClusterRoles error: %v", err) return err } - a.Log.Debug("Cluster roles completed") + a.Log.Debug("cluster roles completed") - for _, csv := range a.csvSet(op.Namespace, v1alpha1.CSVPhaseAny) { + set := a.csvSet(op.Namespace, v1alpha1.CSVPhaseAny) + providedAPIs := make(resolver.APISet) + for _, csv := range set { + logger := logger.WithField("csv", csv.GetName()) origCSVannotations := csv.GetAnnotations() a.addOperatorGroupAnnotations(&csv.ObjectMeta, op, !csv.IsCopied()) - if reflect.DeepEqual(origCSVannotations, csv.GetAnnotations()) == false { + if !reflect.DeepEqual(origCSVannotations, csv.GetAnnotations()) { // CRDs don't support strategic merge patching, but in the future if they do this should be updated to patch if _, err := a.client.OperatorsV1alpha1().ClusterServiceVersions(csv.GetNamespace()).Update(csv); err != nil { - a.Log.Errorf("Update for existing CSV failed: %v", err) + // TODO: return an error and requeue the OperatorGroup here? Can this cause an update to never happen if there's resource contention? + logger.WithError(err).Warnf("update to existing csv failed") + continue } } + + // Don't union providedAPIs if the CSV is copied (member of another OperatorGroup) + if csv.IsCopied() { + logger.Debug("csv is copied. not including in operatorgroup's provided api set") + continue + } + + // TODO: Throw out CSVs that aren't members of the group due to group related failures? + + // Union the providedAPIs from existing members of the group + operatorSurface, err := resolver.NewOperatorFromCSV(csv) + if err != nil { + logger.WithError(err).Warn("could not create OperatorSurface from csv") + continue + } + providedAPIs = providedAPIs.Union(operatorSurface.ProvidedAPIs().StripPlural()) + } + logger.Debug("csv annotation completed") + + // Don't prune providedAPIs if static + if op.Spec.StaticProvidedAPIs { + a.Log.Debug("group has static provided apis. skipping provided api pruning") + return nil + } + + // Prune providedAPIs annotation if the cluster has less providedAPIs (handles CSV deletion) + groupSurface := resolver.NewOperatorGroup(*op) + groupProvidedAPIs := groupSurface.ProvidedAPIs() + if intersection := groupProvidedAPIs.Intersection(providedAPIs); len(intersection) < len(groupProvidedAPIs) { + difference := groupProvidedAPIs.Difference(intersection) + logger := logger.WithFields(logrus.Fields{ + "providedAPIsOnCluster": providedAPIs, + "providedAPIsAnnotation": groupProvidedAPIs, + "providedAPIDifference": difference, + "intersection": intersection, + }) + + // Don't need to check for nil annotations since we already know |annotations| > 0 + annotations := op.GetAnnotations() + annotations[v1alpha2.OperatorGroupProvidedAPIsAnnotationKey] = intersection.String() + op.SetAnnotations(annotations) + logger.Debug("removing provided apis from annotation to match cluster state") + if _, err := a.client.OperatorsV1alpha2().OperatorGroups(op.GetNamespace()).Update(op); err != nil && !k8serrors.IsNotFound(err) { + logger.WithError(err).Warn("could not update provided api annotations") + return nil + } + } + + // Requeue all CSVs that provide the same APIs (including those removed). This notifies conflicting CSVs in + // intersecting groups that their conflict has possibly been resolved, either through resizing or through + // deletion of the conflicting CSV. + csvs, err := a.findCSVsThatProvideAnyOf(providedAPIs.Union(groupProvidedAPIs)) + if err != nil { + logger.WithError(err).Warn("could not find csvs that provide group apis") + } + for _, csv := range csvs { + logger.WithFields(logrus.Fields{ + "csv": csv.GetName(), + "namespace": csv.GetNamespace(), + }).Debug("requeueing provider") + if err := a.csvQueueSet.Requeue(csv.GetName(), csv.GetNamespace()); err != nil { + logger.WithError(err).Warn("could not requeue provider") + } } - a.Log.Debug("CSV annotation completed") return nil } @@ -155,6 +228,7 @@ func (a *Operator) ensureRBACInTargetNamespace(csv *v1alpha1.ClusterServiceVersi // if OperatorGroup is global (all namespaces) we generate cluster roles / cluster role bindings instead if len(targetNamespaces) == 1 && targetNamespaces[0] == corev1.NamespaceAll { + // TODO: We we arent using perms why are we iterating over these? for _, p := range opPerms { if err := a.ensureSingletonRBAC(operatorGroup.GetNamespace(), csv, *p); err != nil { return err @@ -166,6 +240,7 @@ func (a *Operator) ensureRBACInTargetNamespace(csv *v1alpha1.ClusterServiceVersi // otherwise, create roles/rolebindings for each target namespace for _, ns := range targetNamespaces { for _, p := range opPerms { + // TODO: See previous TODO if err := a.ensureTenantRBAC(operatorGroup.GetNamespace(), ns, csv, *p); err != nil { return err } @@ -174,6 +249,7 @@ func (a *Operator) ensureRBACInTargetNamespace(csv *v1alpha1.ClusterServiceVersi return nil } +// TODO: Why is this taking permissions and not using it? func (a *Operator) ensureSingletonRBAC(operatorNamespace string, csv *v1alpha1.ClusterServiceVersion, permissions resolver.OperatorPermissions) error { ownerSelector := ownerutil.CSVOwnerSelector(csv) ownedRoles, err := a.lister.RbacV1().RoleLister().Roles(operatorNamespace).List(ownerSelector) @@ -245,6 +321,7 @@ func (a *Operator) ensureSingletonRBAC(operatorNamespace string, csv *v1alpha1.C return nil } +// TODO: Why is this taking permissions and not using it? func (a *Operator) ensureTenantRBAC(operatorNamespace, targetNamespace string, csv *v1alpha1.ClusterServiceVersion, permissions resolver.OperatorPermissions) error { ownerSelector := ownerutil.CSVOwnerSelector(csv) ownedRoles, err := a.lister.RbacV1().RoleLister().Roles(operatorNamespace).List(ownerSelector) @@ -345,7 +422,6 @@ func (a *Operator) copyCsvToTargetNamespace(csv *v1alpha1.ClusterServiceVersion, } } - continue } else if k8serrors.IsNotFound(err) { newCSV.SetNamespace(ns) newCSV.SetResourceVersion("") @@ -487,3 +563,29 @@ func (a *Operator) ensureOpGroupClusterRoles(op *v1alpha2.OperatorGroup) error { } return nil } + +func (a *Operator) findCSVsThatProvideAnyOf(provide resolver.APISet) ([]*v1alpha1.ClusterServiceVersion, error) { + csvs, err := a.lister.OperatorsV1alpha1().ClusterServiceVersionLister().ClusterServiceVersions(metav1.NamespaceAll).List(labels.Everything()) + if err != nil { + return nil, err + } + + providers := []*v1alpha1.ClusterServiceVersion{} + for i := 0; i < len(csvs); i++ { + csv := csvs[i] + if csv.IsCopied() { + continue + } + + operatorSurface, err := resolver.NewOperatorFromCSV(csv) + if err != nil { + continue + } + + if len(operatorSurface.ProvidedAPIs().StripPlural().Intersection(provide)) > 0 { + providers = append(providers, csv) + } + } + + return providers, nil +} \ No newline at end of file diff --git a/pkg/controller/operators/olm/requirements_test.go b/pkg/controller/operators/olm/requirements_test.go index 69866cf39b..5c13494a05 100644 --- a/pkg/controller/operators/olm/requirements_test.go +++ b/pkg/controller/operators/olm/requirements_test.go @@ -349,8 +349,8 @@ func TestRequirementAndPermissionStatus(t *testing.T) { }, nil, ), - []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, - []*v1beta1.CustomResourceDefinition{crd("c2", "v1")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v1", "g1")}, + []*v1beta1.CustomResourceDefinition{crd("c2", "v1", "g2")}, v1alpha1.CSVPhasePending, ), existingObjs: []runtime.Object{ @@ -395,8 +395,8 @@ func TestRequirementAndPermissionStatus(t *testing.T) { }, }, existingExtObjs: []runtime.Object{ - crd("c1", "v1"), - crd("c2", "v1"), + crd("c1", "v1", "g1"), + crd("c2", "v1", "g2"), }, met: true, expectedRequirementStatuses: map[gvkn]v1alpha1.RequirementStatus{ @@ -428,18 +428,18 @@ func TestRequirementAndPermissionStatus(t *testing.T) { }, }, }, - {"apiextensions.k8s.io", "v1beta1", "CustomResourceDefinition", "c1group"}: { + {"apiextensions.k8s.io", "v1beta1", "CustomResourceDefinition", "c1.g1"}: { Group: "apiextensions.k8s.io", Version: "v1beta1", Kind: "CustomResourceDefinition", - Name: "c1group", + Name: "c1.g1", Status: v1alpha1.RequirementStatusReasonPresent, }, - {"apiextensions.k8s.io", "v1beta1", "CustomResourceDefinition", "c2group"}: { + {"apiextensions.k8s.io", "v1beta1", "CustomResourceDefinition", "c2.g2"}: { Group: "apiextensions.k8s.io", Version: "v1beta1", Kind: "CustomResourceDefinition", - Name: "c2group", + Name: "c2.g2", Status: v1alpha1.RequirementStatusReasonPresent, }, {"operators.coreos.com", "v1alpha1", "ClusterServiceVersion", "csv1"}: { @@ -459,21 +459,21 @@ func TestRequirementAndPermissionStatus(t *testing.T) { "0.0.0", "", installStrategy("csv1-dep", nil, nil), - []*v1beta1.CustomResourceDefinition{crd("c1", "v2")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v2", "g1")}, nil, v1alpha1.CSVPhasePending, ), existingObjs: nil, existingExtObjs: []runtime.Object{ - crd("c1", "v1"), + crd("c1", "v1", "g1"), }, met: false, expectedRequirementStatuses: map[gvkn]v1alpha1.RequirementStatus{ - {"apiextensions.k8s.io", "v1beta1", "CustomResourceDefinition", "c1group"}: { + {"apiextensions.k8s.io", "v1beta1", "CustomResourceDefinition", "c1.g1"}: { Group: "apiextensions.k8s.io", Version: "v1beta1", Kind: "CustomResourceDefinition", - Name: "c1group", + Name: "c1.g1", Status: v1alpha1.RequirementStatusReasonNotPresent, }, {"operators.coreos.com", "v1alpha1", "ClusterServiceVersion", "csv1"}: { @@ -493,21 +493,21 @@ func TestRequirementAndPermissionStatus(t *testing.T) { "0.0.0", "", installStrategy("csv1-dep", nil, nil), - []*v1beta1.CustomResourceDefinition{crd("c1", "version-not-found")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "version-not-found", "g1")}, nil, v1alpha1.CSVPhasePending, ), existingObjs: nil, existingExtObjs: []runtime.Object{ - crd("c1", "v2"), + crd("c1", "v2", "g1"), }, met: false, expectedRequirementStatuses: map[gvkn]v1alpha1.RequirementStatus{ - {"apiextensions.k8s.io", "v1beta1", "CustomResourceDefinition", "c1group"}: { + {"apiextensions.k8s.io", "v1beta1", "CustomResourceDefinition", "c1.g1"}: { Group: "apiextensions.k8s.io", Version: "v1beta1", Kind: "CustomResourceDefinition", - Name: "c1group", + Name: "c1.g1", Status: v1alpha1.RequirementStatusReasonNotAvailable, }, {"operators.coreos.com", "v1alpha1", "ClusterServiceVersion", "csv1"}: { @@ -527,14 +527,14 @@ func TestRequirementAndPermissionStatus(t *testing.T) { "0.0.0", "", installStrategy("csv1-dep", nil, nil), - []*v1beta1.CustomResourceDefinition{crd("c1", "v2")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v2", "g1")}, nil, v1alpha1.CSVPhasePending, ), existingObjs: nil, existingExtObjs: []runtime.Object{ func() *v1beta1.CustomResourceDefinition { - newCRD := crd("c1", "v2") + newCRD := crd("c1", "v2", "g1") // condition order: established, name accepted newCRD.Status.Conditions[0].Status = v1beta1.ConditionTrue newCRD.Status.Conditions[1].Status = v1beta1.ConditionFalse @@ -543,11 +543,11 @@ func TestRequirementAndPermissionStatus(t *testing.T) { }, met: false, expectedRequirementStatuses: map[gvkn]v1alpha1.RequirementStatus{ - {"apiextensions.k8s.io", "v1beta1", "CustomResourceDefinition", "c1group"}: { + {"apiextensions.k8s.io", "v1beta1", "CustomResourceDefinition", "c1.g1"}: { Group: "apiextensions.k8s.io", Version: "v1beta1", Kind: "CustomResourceDefinition", - Name: "c1group", + Name: "c1.g1", Status: v1alpha1.RequirementStatusReasonNotAvailable, }, {"operators.coreos.com", "v1alpha1", "ClusterServiceVersion", "csv1"}: { @@ -567,14 +567,14 @@ func TestRequirementAndPermissionStatus(t *testing.T) { "0.0.0", "", installStrategy("csv1-dep", nil, nil), - []*v1beta1.CustomResourceDefinition{crd("c1", "v2")}, + []*v1beta1.CustomResourceDefinition{crd("c1", "v2", "g1")}, nil, v1alpha1.CSVPhasePending, ), existingObjs: nil, existingExtObjs: []runtime.Object{ func() *v1beta1.CustomResourceDefinition { - newCRD := crd("c1", "v2") + newCRD := crd("c1", "v2", "g1") // condition order: established, name accepted newCRD.Status.Conditions[0].Status = v1beta1.ConditionFalse newCRD.Status.Conditions[1].Status = v1beta1.ConditionTrue @@ -583,11 +583,11 @@ func TestRequirementAndPermissionStatus(t *testing.T) { }, met: false, expectedRequirementStatuses: map[gvkn]v1alpha1.RequirementStatus{ - {"apiextensions.k8s.io", "v1beta1", "CustomResourceDefinition", "c1group"}: { + {"apiextensions.k8s.io", "v1beta1", "CustomResourceDefinition", "c1.g1"}: { Group: "apiextensions.k8s.io", Version: "v1beta1", Kind: "CustomResourceDefinition", - Name: "c1group", + Name: "c1.g1", Status: v1alpha1.RequirementStatusReasonNotAvailable, }, {"operators.coreos.com", "v1alpha1", "ClusterServiceVersion", "csv1"}: { @@ -612,7 +612,7 @@ func TestRequirementAndPermissionStatus(t *testing.T) { test.existingObjs = append(test.existingObjs, namespaceObj) stopCh := make(chan struct{}) defer func() { stopCh <- struct{}{} }() - op, _, err := NewFakeOperator([]runtime.Object{test.csv}, test.existingObjs, test.existingExtObjs, nil, &install.StrategyResolver{}, []string{namespace}, stopCh) + op, _, err := NewFakeOperator([]runtime.Object{test.csv}, test.existingObjs, test.existingExtObjs, nil, &install.StrategyResolver{}, nil, []string{namespace}, stopCh) require.NoError(t, err) // Get the permission status diff --git a/pkg/controller/registry/resolver/groups.go b/pkg/controller/registry/resolver/groups.go new file mode 100644 index 0000000000..5972fda1d2 --- /dev/null +++ b/pkg/controller/registry/resolver/groups.go @@ -0,0 +1,168 @@ +//go:generate counterfeiter -o ../../../fakes/fake_api_intersection_reconciler.go . APIIntersectionReconciler +package resolver + +import ( + "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha2" +) + +type NamespaceSet map[string]struct{} + +func NewNamespaceSet(namespaces []string) NamespaceSet { + set := make(NamespaceSet) + for _, namespace := range namespaces { + set[namespace] = struct{}{} + } + + return set +} + +func (n NamespaceSet) Peek() string { + for namespace := range n { + return namespace + } + + return "" +} + +func (n NamespaceSet) Intersection(set NamespaceSet) NamespaceSet { + intersection := make(NamespaceSet) + // Handle special NamespaceAll cases + if len(n) == 1 && n.Peek() == "" { + for namespace := range set { + intersection[namespace] = struct{}{} + } + return intersection + } + if len(set) == 1 && set.Peek() == "" { + for namespace := range n { + intersection[namespace] = struct{}{} + } + return intersection + } + + for namespace := range n { + if _, ok := set[namespace]; ok { + intersection[namespace] = struct{}{} + } + } + + return intersection +} + +type OperatorGroupSurface interface { + Identifier() string + Namespace() string + Targets() NamespaceSet + ProvidedAPIs() APISet + GroupIntersection(groups ...OperatorGroupSurface) []OperatorGroupSurface +} + +var _ OperatorGroupSurface = &OperatorGroup{} + +type OperatorGroup struct { + namespace string + name string + targets NamespaceSet + providedAPIs APISet +} + +func NewOperatorGroup(group v1alpha2.OperatorGroup) *OperatorGroup { + // Add operatorgroup namespace if not NamespaceAll + namespaces := group.Status.Namespaces + if len(namespaces) >= 1 && namespaces[0] != "" { + namespaces = append(namespaces, group.GetNamespace()) + } + // TODO: Sanitize OperatorGroup if len(namespaces) > 1 and contains "" + gvksStr := group.GetAnnotations()[v1alpha2.OperatorGroupProvidedAPIsAnnotationKey] + + return &OperatorGroup{ + namespace: group.GetNamespace(), + name: group.GetName(), + targets: NewNamespaceSet(namespaces), + providedAPIs: GVKStringToProvidedAPISet(gvksStr), + } +} + +func NewOperatorGroupSurfaces(groups ...v1alpha2.OperatorGroup) []OperatorGroupSurface { + operatorGroups := make([]OperatorGroupSurface, len(groups)) + for i, group := range groups { + operatorGroups[i] = NewOperatorGroup(group) + } + + return operatorGroups +} + +func (g *OperatorGroup) Identifier() string { + return g.name + "/" + g.namespace +} + +func (g *OperatorGroup) Namespace() string { + return g.namespace +} + +func (g *OperatorGroup) Targets() NamespaceSet { + return g.targets +} + +func (g *OperatorGroup) ProvidedAPIs() APISet { + return g.providedAPIs +} + +func (g *OperatorGroup) GroupIntersection(groups ...OperatorGroupSurface) []OperatorGroupSurface { + intersection := []OperatorGroupSurface{} + for _, group := range groups { + if group.Identifier() == g.Identifier() { + // Skip self if present + continue + } + if len(g.targets.Intersection(group.Targets())) > 0 { + // TODO: This uses tons of space - maps are copied every time + intersection = append(intersection, group) + } + } + + return intersection +} + +type APIReconciliationResult int + +const ( + RemoveAPIs APIReconciliationResult = iota + AddAPIs + APIConflict + NoAPIConflict +) + +type APIIntersectionReconciler interface { + Reconcile(add APISet, group OperatorGroupSurface, otherGroups ...OperatorGroupSurface) (APIReconciliationResult) +} + +type APIIntersectionReconcileFunc func(add APISet, group OperatorGroupSurface, otherGroups ...OperatorGroupSurface) (APIReconciliationResult) +func (a APIIntersectionReconcileFunc) Reconcile(add APISet, group OperatorGroupSurface, otherGroups ...OperatorGroupSurface) (APIReconciliationResult) { + return a(add, group, otherGroups...) +} + +func ReconcileAPIIntersection(add APISet, group OperatorGroupSurface, otherGroups ...OperatorGroupSurface) (APIReconciliationResult) { + groupIntersection := group.GroupIntersection(otherGroups...) + providedAPIIntersection := make(APISet) + for _, g := range groupIntersection { + providedAPIIntersection = providedAPIIntersection.Union(g.ProvidedAPIs()) + } + + intersecting := len(add.Intersection(providedAPIIntersection)) > 0 + subset := add.IsSubset(group.ProvidedAPIs()) + + if subset && intersecting { + return RemoveAPIs + } + + if !subset && intersecting { + return APIConflict + } + + if !subset { + return AddAPIs + } + + return NoAPIConflict +} \ No newline at end of file diff --git a/pkg/controller/registry/resolver/groups_test.go b/pkg/controller/registry/resolver/groups_test.go new file mode 100644 index 0000000000..3cc4433af3 --- /dev/null +++ b/pkg/controller/registry/resolver/groups_test.go @@ -0,0 +1,804 @@ +package resolver + +import ( + "testing" + "strings" + + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + opregistry "github.com/operator-framework/operator-registry/pkg/registry" + + "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha2" +) + +func buildAPIOperatorGroup(namespace, name string, targets []string, gvks []string) v1alpha2.OperatorGroup { + return v1alpha2.OperatorGroup { + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: name, + Annotations: map[string]string{ + v1alpha2.OperatorGroupProvidedAPIsAnnotationKey: strings.Join(gvks, ","), + }, + }, + Status: v1alpha2.OperatorGroupStatus{ + Namespaces: targets, + }, + } +} +func TestNewOperatorGroup(t *testing.T) { + tests := []struct { + name string + in v1alpha2.OperatorGroup + want *OperatorGroup + }{ + { + name: "NoTargetNamespaces/NoProvidedAPIs", + in: buildAPIOperatorGroup("ns", "empty-group", nil, nil), + want: &OperatorGroup{ + namespace: "ns", + name: "empty-group", + targets: make(NamespaceSet), + providedAPIs: make(APISet), + }, + }, + { + name: "OneTargetNamespace/NoProvidedAPIs", + in: buildAPIOperatorGroup("ns", "empty-group", []string{"ns-1"}, nil), + want: &OperatorGroup{ + namespace: "ns", + name: "empty-group", + targets: NamespaceSet{ + "ns": {}, + "ns-1": {}, + }, + providedAPIs: make(APISet), + }, + }, + { + name: "OwnTargetNamespace/NoProvidedAPIs", + in: buildAPIOperatorGroup("ns", "empty-group", []string{"ns"}, nil), + want: &OperatorGroup{ + namespace: "ns", + name: "empty-group", + targets: NamespaceSet{ + "ns": {}, + }, + providedAPIs: make(APISet), + }, + }, + { + name: "MultipleTargetNamespaces/NoProvidedAPIs", + in: buildAPIOperatorGroup("ns", "empty-group", []string{"ns-1", "ns-2"}, nil), + want: &OperatorGroup{ + namespace: "ns", + name: "empty-group", + targets: NamespaceSet{ + "ns": {}, + "ns-1": {}, + "ns-2" :{}, + }, + providedAPIs: make(APISet), + }, + }, + { + name: "AllTargetNamespaces/NoProvidedAPIs", + in: buildAPIOperatorGroup("ns", "empty-group", []string{metav1.NamespaceAll}, nil), + want: &OperatorGroup{ + namespace: "ns", + name: "empty-group", + targets: NamespaceSet{ + metav1.NamespaceAll: {}, + }, + providedAPIs: make(APISet), + }, + }, + { + name: "OneTargetNamespace/OneProvidedAPI", + in: buildAPIOperatorGroup("ns", "group", []string{"ns-1"}, []string{"Goose.v1alpha1.birds.com"}), + want: &OperatorGroup{ + namespace: "ns", + name: "group", + targets: NamespaceSet{ + "ns": {}, + "ns-1": {}, + }, + providedAPIs: APISet{ + opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, + }, + }, + }, + { + name: "OneTargetNamespace/BadProvidedAPI", + in: buildAPIOperatorGroup("ns", "group", []string{"ns-1"}, []string{"Goose.v1alpha1"}), + want: &OperatorGroup{ + namespace: "ns", + name: "group", + targets: NamespaceSet{ + "ns": {}, + "ns-1": {}, + }, + providedAPIs: make(APISet), + }, + }, + { + name: "OneTargetNamespace/MultipleProvidedAPIs/OneBad", + in: buildAPIOperatorGroup("ns", "group", []string{"ns-1"}, []string{"Goose.v1alpha1,Moose.v1alpha1.mammals.com"}), + want: &OperatorGroup{ + namespace: "ns", + name: "group", + targets: NamespaceSet{ + "ns": {}, + "ns-1": {}, + }, + providedAPIs: APISet{ + opregistry.APIKey{Group: "mammals.com", Version: "v1alpha1", Kind: "Moose"}: {}, + }, + }, + }, + { + name: "OneTargetNamespace/MultipleProvidedAPIs", + in: buildAPIOperatorGroup("ns", "group", []string{"ns-1"}, []string{"Goose.v1alpha1.birds.com,Moose.v1alpha1.mammals.com"}), + want: &OperatorGroup{ + namespace: "ns", + name: "group", + targets: NamespaceSet{ + "ns": {}, + "ns-1": {}, + }, + providedAPIs: APISet{ + opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, + opregistry.APIKey{Group: "mammals.com", Version: "v1alpha1", Kind: "Moose"}: {}, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + group := NewOperatorGroup(tt.in) + require.NotNil(t, group) + require.EqualValues(t, tt.want, group) + }) + } +} + +func TestNamespaceSetIntersection(t *testing.T) { + type input struct { + left NamespaceSet + right NamespaceSet + } + tests := []struct{ + name string + in input + want NamespaceSet + }{ + { + name: "EmptySets", + in: input{ + left: make(NamespaceSet), + right: make(NamespaceSet), + }, + want: make(NamespaceSet), + }, + { + name: "EmptyLeft/MultipleRight/NoIntersection", + in: input{ + left: make(NamespaceSet), + right: NamespaceSet{ + "ns": {}, + "ns-1": {}, + "ns-2": {}, + }, + }, + want: make(NamespaceSet), + }, + { + name: "MultipleLeft/EmptyRight/NoIntersection", + in: input{ + left: NamespaceSet{ + "ns": {}, + "ns-1": {}, + "ns-2": {}, + }, + right: make(NamespaceSet), + }, + want: make(NamespaceSet), + }, + { + name: "OneLeft/OneRight/Intersection", + in: input{ + left: NamespaceSet{ + "ns": {}, + }, + right: NamespaceSet{ + "ns": {}, + }, + }, + want: NamespaceSet{ + "ns": {}, + }, + }, + { + name: "MultipleLeft/MultipleRight/SomeIntersect", + in: input{ + left: NamespaceSet{ + "ns": {}, + "ns-1": {}, + "ns-2": {}, + }, + right: NamespaceSet{ + "ns": {}, + "ns-1": {}, + "ns-3": {}, + }, + }, + want: NamespaceSet{ + "ns": {}, + "ns-1": {}, + }, + }, + { + name: "MultipleLeft/MultipleRight/AllIntersect", + in: input{ + left: NamespaceSet{ + "ns": {}, + "ns-1": {}, + "ns-2": {}, + }, + right: NamespaceSet{ + "ns": {}, + "ns-1": {}, + "ns-2": {}, + }, + }, + want: NamespaceSet{ + "ns": {}, + "ns-1": {}, + "ns-2": {}, + }, + }, + { + name: "AllLeft/MultipleRight/RightIsIntersection", + in: input{ + left: NamespaceSet{ + "": {}, + }, + right: NamespaceSet{ + "ns": {}, + "ns-1": {}, + "ns-2": {}, + }, + }, + want: NamespaceSet{ + "ns": {}, + "ns-1": {}, + "ns-2": {}, + }, + }, + { + name: "MultipleLeft/AllRight/LeftIsIntersection", + in: input{ + left: NamespaceSet{ + "ns": {}, + "ns-1": {}, + "ns-2": {}, + }, + right: NamespaceSet{ + "": {}, + }, + }, + want: NamespaceSet{ + "ns": {}, + "ns-1": {}, + "ns-2": {}, + }, + }, + { + name: "AllLeft/EmptyRight/NoIntersection", + in: input{ + left: NamespaceSet{ + "": {}, + }, + right: make(NamespaceSet), + }, + want: make(NamespaceSet), + }, + { + name: "EmptyLeft/AllRight/NoIntersection", + in: input{ + left: make(NamespaceSet), + right: NamespaceSet{ + "": {}, + }, + }, + want: make(NamespaceSet), + }, + { + name: "AllLeft/AllRight/Intersection", + in: input{ + left: NamespaceSet{ + "": {}, + }, + right: NamespaceSet{ + "": {}, + }, + }, + want: NamespaceSet{ + "": {}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.EqualValues(t, tt.want, tt.in.left.Intersection(tt.in.right)) + }) + } +} + +func buildOperatorGroup(namespace, name string, targets []string, gvks []string) *OperatorGroup { + return NewOperatorGroup(buildAPIOperatorGroup(namespace, name, targets, gvks)) +} + +func TestGroupIntersection(t *testing.T) { + type input struct { + left OperatorGroupSurface + right []OperatorGroupSurface + } + tests := []struct{ + name string + in input + want []OperatorGroupSurface + }{ + { + name: "NoTargets/NilGroups/NoIntersection", + in: input{ + left: buildOperatorGroup("ns", "empty-group", nil, nil), + right: nil, + }, + want: []OperatorGroupSurface{}, + }, + { + name: "MatchingTarget/SingleOtherGroup/Intersection", + in: input{ + left: buildOperatorGroup("ns", "group-a", []string{"ns-1"}, nil), + right: []OperatorGroupSurface{ + buildOperatorGroup("ns-2", "group-b", []string{"ns-1"}, nil), + }, + }, + want: []OperatorGroupSurface{ + buildOperatorGroup("ns-2", "group-b", []string{"ns-1"}, nil), + }, + }, + { + name: "TargetIsOperatorNamespace/SingleOtherGroup/Intersection", + in: input{ + left: buildOperatorGroup("ns", "group-a", []string{"ns-1"}, nil), + right: []OperatorGroupSurface{ + buildOperatorGroup("ns-2", "group-b", []string{"ns"}, nil), + }, + }, + want: []OperatorGroupSurface{ + buildOperatorGroup("ns-2", "group-b", []string{"ns"}, nil), + }, + }, + { + name: "MatchingOperatorNamespaces/SingleOtherGroup/Intersection", + in: input{ + left: buildOperatorGroup("ns", "group-a", []string{"ns-1"}, nil), + right: []OperatorGroupSurface{ + buildOperatorGroup("ns", "group-b", []string{"ns-2"}, nil), + }, + }, + want: []OperatorGroupSurface{ + buildOperatorGroup("ns", "group-b", []string{"ns-2"}, nil), + }, + }, + { + name: "MatchingTarget/MultipleOtherGroups/Intersection", + in: input{ + left: buildOperatorGroup("ns", "group-a", []string{"ns-1"}, nil), + right: []OperatorGroupSurface{ + buildOperatorGroup("ns-2", "group-b", []string{"ns-1"}, nil), + buildOperatorGroup("ns-3", "group-c", []string{"ns-1"}, nil), + }, + }, + want: []OperatorGroupSurface{ + buildOperatorGroup("ns-2", "group-b", []string{"ns-1"}, nil), + buildOperatorGroup("ns-3", "group-c", []string{"ns-1"}, nil), + }, + }, + { + name: "NonMatchingTargets/MultipleOtherGroups/NoIntersection", + in: input{ + left: buildOperatorGroup("ns", "group-a", []string{"ns-1", "ns-2", "ns-3"}, nil), + right: []OperatorGroupSurface{ + buildOperatorGroup("ns-4", "group-b", []string{"ns-6", "ns-7", "ns-8"}, nil), + buildOperatorGroup("ns-5", "group-c", []string{"ns-6", "ns-7", "ns-8"}, nil), + }, + }, + want: []OperatorGroupSurface{}, + }, + { + name: "AllNamespaces/MultipleTargets/Intersection", + in: input{ + left: buildOperatorGroup("ns", "group-a", []string{""}, nil), + right: []OperatorGroupSurface{ + buildOperatorGroup("ns-4", "group-b", []string{"ns-6", "ns-7", "ns-8"}, nil), + buildOperatorGroup("ns-5", "group-c", []string{"ns-9", "ns-10", "ns-11"}, nil), + buildOperatorGroup("ns-6", "group-d", []string{"ns-11", "ns-12"}, nil), + }, + }, + want: []OperatorGroupSurface{ + buildOperatorGroup("ns-4", "group-b", []string{"ns-6", "ns-7", "ns-8"}, nil), + buildOperatorGroup("ns-5", "group-c", []string{"ns-9", "ns-10", "ns-11"}, nil), + buildOperatorGroup("ns-6", "group-d", []string{"ns-11", "ns-12"}, nil), + }, + }, + { + name: "MatchingTargetAllNamespace/MultipleTargets/Intersection", + in: input{ + left: buildOperatorGroup("ns", "group-a", []string{"ns-1", "ns-2", "ns-3"}, nil), + right: []OperatorGroupSurface{ + buildOperatorGroup("ns-4", "group-b", []string{""}, nil), + buildOperatorGroup("ns-5", "group-c", []string{"ns-9", "ns-10", "ns-11"}, nil), + buildOperatorGroup("ns-6", "group-d", []string{"ns-11", "ns-12"}, nil), + }, + }, + want: []OperatorGroupSurface{ + buildOperatorGroup("ns-4", "group-b", []string{""}, nil), + }, + }, + { + name: "AllNamespace/MultipleTargets/OneAllNamespace/Intersection", + in: input{ + left: buildOperatorGroup("ns", "group-a", []string{""}, nil), + right: []OperatorGroupSurface{ + buildOperatorGroup("ns-4", "group-b", []string{""}, nil), + buildOperatorGroup("ns-5", "group-c", []string{"ns-9", "ns-10", "ns-11"}, nil), + buildOperatorGroup("ns-6", "group-d", []string{"ns-11", "ns-12"}, nil), + }, + }, + want: []OperatorGroupSurface{ + buildOperatorGroup("ns-4", "group-b", []string{""}, nil), + buildOperatorGroup("ns-5", "group-c", []string{"ns-9", "ns-10", "ns-11"}, nil), + buildOperatorGroup("ns-6", "group-d", []string{"ns-11", "ns-12"}, nil), + }, + }, + { + name: "AllNamespace/AllNamespace/Intersection", + in: input{ + left: buildOperatorGroup("ns", "group-a", []string{""}, nil), + right: []OperatorGroupSurface{ + buildOperatorGroup("ns-4", "group-b", []string{""}, nil), + }, + }, + want: []OperatorGroupSurface{ + buildOperatorGroup("ns-4", "group-b", []string{""}, nil), + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.EqualValues(t, tt.want, tt.in.left.GroupIntersection(tt.in.right...)) + }) + } + +} + +func apiIntersectionReconcilerSuite(t *testing.T, reconciler APIIntersectionReconciler) { + tests := []struct{ + name string + add APISet + group OperatorGroupSurface + otherGroups []OperatorGroupSurface + want APIReconciliationResult + }{ + { + name: "Empty/NoAPIConflict", + add: make(APISet), + group: buildOperatorGroup("ns", "g1", []string{"ns"}, nil), + otherGroups: nil, + want: NoAPIConflict, + }, + { + name: "NoNamespaceIntersection/APIIntersection/NoAPIConflict", + add: APISet{ + opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, + }, + group: buildOperatorGroup("ns", "g1", []string{"ns-1"}, []string{"Goose.v1alpha1.birds.com"}), + otherGroups: []OperatorGroupSurface{ + buildOperatorGroup("ns-2", "g1", []string{"ns-3"}, []string{"Goose.v1alpha1.birds.com"}), + }, + want: NoAPIConflict, + }, + { + name: "NamespaceIntersection/NoAPIIntersection/NoAPIConflict", + add: APISet{ + opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, + }, + group: buildOperatorGroup("ns", "g1", []string{"ns-1"}, []string{"Goose.v1alpha1.birds.com"}), + otherGroups: []OperatorGroupSurface{ + buildOperatorGroup("ns-2", "g1", []string{"ns-1"}, []string{"Moose.v1alpha1.mammals.com"}), + }, + want: NoAPIConflict, + }, + { + name: "MultipleNamespaceIntersections/NoAPIIntersection/NoAPIConflict", + add: APISet{ + opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, + }, + group: buildOperatorGroup("ns", "g1", []string{"ns-1"}, []string{"Goose.v1alpha1.birds.com"}), + otherGroups: []OperatorGroupSurface{ + buildOperatorGroup("ns-2", "g1", []string{"ns-1"}, []string{"Moose.v1alpha1.mammals.com"}), + buildOperatorGroup("ns-2", "g1", []string{"ns"}, []string{"Egret.v1alpha1.birds.com"}), + }, + want: NoAPIConflict, + }, + { + name: "SomeNamespaceIntersection/NoAPIIntersection/NoAPIConflict", + add: APISet{ + opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, + opregistry.APIKey{Group: "mammals.com", Version: "v1alpha1", Kind: "Moose"}: {}, + }, + group: buildOperatorGroup("ns", "g1", []string{"ns-1", "ns-2", "ns-3"}, []string{"Goose.v1alpha1.birds.com,Moose.v1alpha1.mammals.com"}), + otherGroups: []OperatorGroupSurface{ + buildOperatorGroup("ns-7", "g1", []string{"ns-4"}, []string{"Moose.v1alpha1.mammals.com"}), + buildOperatorGroup("ns-8", "g1", []string{"ns-5"}, []string{"Goose.v1alpha1.birds.com"}), + buildOperatorGroup("ns-9", "g1", []string{""}, []string{"Goat.v1alpha1.mammals.com"}), + }, + want: NoAPIConflict, + }, + { + name: "AllNamespaceIntersection/NoAPIIntersection/NoAPIConflict", + add: APISet{ + opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, + }, + group: buildOperatorGroup("ns", "g1", []string{""}, []string{"Goose.v1alpha1.birds.com"}), + otherGroups: []OperatorGroupSurface{ + buildOperatorGroup("ns-2", "g1", []string{"ns-1"}, []string{"Moose.v1alpha1.mammals.com"}), + }, + want: NoAPIConflict, + }, + { + name: "AllNamespaceIntersectionOnOther/NoAPIIntersection/NoAPIConflict", + add: APISet{ + opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, + }, + group: buildOperatorGroup("ns", "g1", []string{"ns-1"}, []string{"Goose.v1alpha1.birds.com"}), + otherGroups: []OperatorGroupSurface{ + buildOperatorGroup("ns-2", "g1", []string{""}, []string{"Moose.v1alpha1.mammals.com"}), + }, + want: NoAPIConflict, + }, + { + name: "AllNamespaceInstersectionOnOther/NoAPIIntersection/NoAPIConflict", + add: APISet{ + opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, + }, + group: buildOperatorGroup("ns", "g1", []string{""}, []string{"Goose.v1alpha1.birds.com"}), + otherGroups: []OperatorGroupSurface{ + buildOperatorGroup("ns-2", "g1", []string{""}, []string{"Moose.v1alpha1.mammals.com"}), + }, + want: NoAPIConflict, + }, + { + name: "NamespaceIntersection/NoAPIIntersection/NoAPIConflict", + add: APISet{ + opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, + }, + group: buildOperatorGroup("ns", "g1", []string{"ns-1"}, []string{"Goose.v1alpha1.birds.com"}), + otherGroups: []OperatorGroupSurface{ + buildOperatorGroup("ns-2", "g1", []string{"ns-1"}, nil), + }, + want: NoAPIConflict, + }, + { + name: "NamespaceIntersection/APIIntersection/APIConflict", + add: APISet{ + opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, + }, + group: buildOperatorGroup("ns", "g1", []string{"ns-1"}, nil), + otherGroups: []OperatorGroupSurface{ + buildOperatorGroup("ns-2", "g1", []string{"ns-1"}, []string{"Goose.v1alpha1.birds.com"}), + }, + want: APIConflict, + }, + { + name: "AllNamespaceIntersection/APIIntersection/APIConflict", + add: APISet{ + opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, + }, + group: buildOperatorGroup("ns", "g1", []string{""}, nil), + otherGroups: []OperatorGroupSurface{ + buildOperatorGroup("ns-2", "g1", []string{"ns-1"}, []string{"Goose.v1alpha1.birds.com"}), + }, + want: APIConflict, + }, + { + name: "AllNamespaceIntersectionOnOther/APIIntersection/APIConflict", + add: APISet{ + opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, + }, + group: buildOperatorGroup("ns", "g1", []string{"ns-1"}, nil), + otherGroups: []OperatorGroupSurface{ + buildOperatorGroup("ns-2", "g1", []string{""}, []string{"Goose.v1alpha1.birds.com"}), + }, + want: APIConflict, + }, + { + name: "AllNamespaceIntersectionOnBoth/APIIntersection/APIConflict", + add: APISet{ + opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, + }, + group: buildOperatorGroup("ns", "g1", []string{""}, nil), + otherGroups: []OperatorGroupSurface{ + buildOperatorGroup("ns-2", "g1", []string{""}, []string{"Goose.v1alpha1.birds.com"}), + }, + want: APIConflict, + }, + { + name: "NamespaceIntersection/SomeAPIIntersection/APIConflict", + add: APISet{ + opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, + }, + group: buildOperatorGroup("ns", "g1", []string{"ns-1"}, nil), + otherGroups: []OperatorGroupSurface{ + buildOperatorGroup("ns-2", "g1", []string{"ns-1"}, []string{"Moose.v1alpha1.birds.com"}), + buildOperatorGroup("ns-3", "g1", []string{"ns-1"}, []string{"Goose.v1alpha1.birds.com,Egret.v1alpha1.birds.com"}), + }, + want: APIConflict, + }, + { + name: "NamespaceIntersectionOnOperatorNamespace/SomeAPIIntersection/APIConflict", + add: APISet{ + opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, + }, + group: buildOperatorGroup("ns", "g1", []string{"ns-1"}, nil), + otherGroups: []OperatorGroupSurface{ + buildOperatorGroup("ns-3", "g1", []string{"ns"}, []string{"Goose.v1alpha1.birds.com,Egret.v1alpha1.birds.com"}), + }, + want: APIConflict, + }, + + { + name: "NoNamespaceIntersection/NoAPIIntersection/AddAPIs", + add: APISet{ + opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, + }, + group: buildOperatorGroup("ns", "g1", []string{"ns-1"}, nil), + otherGroups: []OperatorGroupSurface{ + buildOperatorGroup("ns-2", "g1", []string{"ns-2"}, []string{"Goose.v1alpha1.birds.com"}), + }, + want: AddAPIs, + }, + { + name: "NamespaceIntersection/NoAPIIntersection/AddAPIs", + add: APISet{ + opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, + }, + group: buildOperatorGroup("ns", "g1", []string{"ns-1"}, nil), + otherGroups: []OperatorGroupSurface{ + buildOperatorGroup("ns-2", "g1", []string{"ns-1"}, []string{"Moose.v1alpha1.mammals.com"}), + }, + want: AddAPIs, + }, + { + name: "OperatorNamespaceIntersection/NoAPIIntersection/AddAPIs", + add: APISet{ + opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, + }, + group: buildOperatorGroup("ns", "g1", []string{"ns-1"}, nil), + otherGroups: []OperatorGroupSurface{ + buildOperatorGroup("ns-2", "g1", []string{"ns"}, []string{"Moose.v1alpha1.mammals.com"}), + }, + want: AddAPIs, + }, + { + name: "AllNamespaceIntersection/NoAPIIntersection/AddAPIs", + add: APISet{ + opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, + }, + group: buildOperatorGroup("ns", "g1", []string{""}, nil), + otherGroups: []OperatorGroupSurface{ + buildOperatorGroup("ns-2", "g1", []string{"ns-1"}, []string{"Moose.v1alpha1.mammals.com"}), + buildOperatorGroup("ns-3", "g1", []string{"ns-1"}, []string{"Goat.v1alpha1.mammals.com,Egret.v1alpha1.birds.com"}), + }, + want: AddAPIs, + }, + { + name: "AllNamespaceIntersectionOnOthers/NoAPIIntersection/AddAPIs", + add: APISet{ + opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, + }, + group: buildOperatorGroup("ns", "g1", []string{"ns-1"}, nil), + otherGroups: []OperatorGroupSurface{ + buildOperatorGroup("ns-2", "g1", []string{""}, []string{"Moose.v1alpha1.mammals.com"}), + buildOperatorGroup("ns-3", "g1", []string{""}, []string{"Goat.v1alpha1.mammals.com,Egret.v1alpha1.birds.com"}), + }, + want: AddAPIs, + }, + { + name: "AllNamespaceIntersectionOnOthers/NoAPIIntersection/AddAPIs/PrexistingAddition", + add: APISet{ + opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, + opregistry.APIKey{Group: "mammals.com", Version: "v1alpha1", Kind: "Cow"}: {}, + }, + group: buildOperatorGroup("ns", "g1", []string{"ns-1"}, []string{"Cow.v1alpha1.mammals.com"}), + otherGroups: []OperatorGroupSurface{ + buildOperatorGroup("ns-2", "g1", []string{""}, []string{"Moose.v1alpha1.mammals.com"}), + buildOperatorGroup("ns-3", "g1", []string{""}, []string{"Goat.v1alpha1.mammals.com,Egret.v1alpha1.birds.com"}), + }, + want: AddAPIs, + }, + { + name: "NamespaceInstersection/APIIntersection/RemoveAPIs", + add: APISet{ + opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, + }, + group: buildOperatorGroup("ns", "g1", []string{"ns-1"}, []string{"Goose.v1alpha1.birds.com"}), + otherGroups: []OperatorGroupSurface{ + buildOperatorGroup("ns-2", "g1", []string{"ns-1"}, []string{"Goose.v1alpha1.birds.com"}), + }, + want: RemoveAPIs, + }, + { + name: "AllNamespaceInstersection/APIIntersection/RemoveAPIs", + add: APISet{ + opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, + }, + group: buildOperatorGroup("ns", "g1", []string{""}, []string{"Goose.v1alpha1.birds.com"}), + otherGroups: []OperatorGroupSurface{ + buildOperatorGroup("ns-2", "g1", []string{"ns-1"}, []string{"Goose.v1alpha1.birds.com"}), + }, + want: RemoveAPIs, + }, + { + name: "AllNamespaceInstersectionOnOther/APIIntersection/RemoveAPIs", + add: APISet{ + opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, + }, + group: buildOperatorGroup("ns", "g1", []string{""}, []string{"Goose.v1alpha1.birds.com"}), + otherGroups: []OperatorGroupSurface{ + buildOperatorGroup("ns-2", "g1", []string{""}, []string{"Goose.v1alpha1.birds.com"}), + }, + want: RemoveAPIs, + }, + { + name: "MultipleNamespaceIntersections/APIIntersection/RemoveAPIs", + add: APISet{ + opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, + }, + group: buildOperatorGroup("ns", "g1", []string{"ns-1"}, []string{"Goose.v1alpha1.birds.com"}), + otherGroups: []OperatorGroupSurface{ + buildOperatorGroup("ns-2", "g1", []string{"ns-1"}, []string{"Goose.v1alpha1.birds.com"}), + buildOperatorGroup("ns-2", "g1", []string{"ns"}, []string{"Goose.v1alpha1.birds.com"}), + }, + want: RemoveAPIs, + }, + { + name: "SomeNamespaceIntersection/APIIntersection/RemoveAPIs", + add: APISet{ + opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, + opregistry.APIKey{Group: "mammals.com", Version: "v1alpha1", Kind: "Moose"}: {}, + }, + group: buildOperatorGroup("ns", "g1", []string{"ns-1", "ns-2", "ns-3"}, []string{"Goose.v1alpha1.birds.com,Moose.v1alpha1.mammals.com"}), + otherGroups: []OperatorGroupSurface{ + buildOperatorGroup("ns-7", "g1", []string{"ns-4"}, []string{"Moose.v1alpha1.mammals.com"}), + buildOperatorGroup("ns-8", "g1", []string{"ns-5", "ns-3"}, []string{"Goose.v1alpha1.birds.com"}), + buildOperatorGroup("ns-9", "g1", []string{""}, []string{"Goat.v1alpha1.mammals.com"}), + }, + want: RemoveAPIs, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.want, reconciler.Reconcile(tt.add, tt.group, tt.otherGroups...)) + }) + } +} +func TestReconcileAPIIntersection(t *testing.T) { + apiIntersectionReconcilerSuite(t, APIIntersectionReconcileFunc(ReconcileAPIIntersection)) +} \ No newline at end of file diff --git a/pkg/controller/registry/resolver/operators.go b/pkg/controller/registry/resolver/operators.go index 061386bb09..864a2f2f81 100644 --- a/pkg/controller/registry/resolver/operators.go +++ b/pkg/controller/registry/resolver/operators.go @@ -3,8 +3,11 @@ package resolver import ( "fmt" "strings" + "sort" + "k8s.io/apimachinery/pkg/runtime/schema" "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1" + opregistry "github.com/operator-framework/operator-registry/pkg/registry" ) @@ -37,6 +40,101 @@ func (s APISet) PopAPIKey() *opregistry.APIKey { return nil } +func GVKStringToProvidedAPISet(gvksStr string) APISet { + set := make(APISet) + // TODO: Should we make gvk strings lowercase to avoid issues with user set gvks? + gvks := strings.Split(strings.Replace(gvksStr, " ", "", -1), ",") + for _, gvkStr := range gvks { + gvk, _ := schema.ParseKindArg(gvkStr) + if gvk != nil { + set[opregistry.APIKey{Group: gvk.Group, Version: gvk.Version, Kind: gvk.Kind}] = struct{}{} + } + } + + return set +} + +func APIKeyToGVKString(key opregistry.APIKey) string { + // TODO: Add better validation of GVK + return strings.Join([]string{key.Kind, key.Version, key.Group}, ".") +} + +func (s APISet) String() string { + gvkStrs := make([]string, len(s)) + i := 0 + for api := range s { + // TODO: Only add valid GVK strings + gvkStrs[i] = APIKeyToGVKString(api) + i++ + } + sort.Strings(gvkStrs) + + return strings.Join(gvkStrs, ",") +} + +// TODO: Generalize set logic and make an abstraction for sets to implemement to feed into it. + +// Union returns the union of the APISet and the given list of APISets +func (s APISet) Union(sets ...APISet) APISet { + union := make(APISet) + for api := range s { + union[api] = struct{}{} + } + for _, set := range sets { + for api := range set { + union[api] = struct{}{} + } + } + + return union +} + +// Intersection returns the intersection of the APISet and the given list of APISets +func (s APISet) Intersection(sets ...APISet) APISet { + intersection := make(APISet) + for _, set := range sets { + for api := range set { + if _, ok := s[api]; ok { + intersection[api] = struct{}{} + } + } + } + + return intersection +} + +func (s APISet) Difference(set APISet) APISet { + difference := make(APISet).Union(s) + for api := range set { + if _, ok := difference[api]; ok { + delete(difference, api) + } + } + + return difference +} + +// IsSubset returns true if the APISet is a subset of the given one +func (s APISet) IsSubset(set APISet) bool { + for api := range s { + if _, ok := set[api]; !ok { + return false + } + } + + return true +} + +// StripPlural returns the APISet with the Plural field of all APIKeys removed +func (s APISet) StripPlural() APISet { + set := make(APISet) + for api := range s { + set[opregistry.APIKey{Group: api.Group, Version: api.Version, Kind: api.Kind}] = struct{}{} + } + + return set +} + type APIOwnerSet map[opregistry.APIKey]OperatorSurface func EmptyAPIOwnerSet() APIOwnerSet { diff --git a/pkg/controller/registry/resolver/operators_test.go b/pkg/controller/registry/resolver/operators_test.go index 1ae4cfb787..02a56489db 100644 --- a/pkg/controller/registry/resolver/operators_test.go +++ b/pkg/controller/registry/resolver/operators_test.go @@ -14,6 +14,686 @@ import ( "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1" ) +func TestGVKStringToProvidedAPISet(t *testing.T) { + tests := []struct{ + name string + in string + want APISet + }{ + { + name: "EmptyString/EmptySet", + in: "", + want: make(APISet), + }, + { + name: "Garbage/EmptySet", + in: ",,,,,alkjahsdfjlh!@#$%", + want: make(APISet), + }, + { + name: "SingleBadGVK/EmptySet", + in: "this-is.not-good", + want: make(APISet), + }, + { + name: "MultipleBadGVK/EmptySet", + in: "this-is.not-good,thisisnoteither", + want: make(APISet), + }, + { + name: "SingleGoodGVK/SingleAPI", + in: "Goose.v1alpha1.birds.com", + want: APISet{ + opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, + }, + }, + { + name: "MutlipleGoodGVK/MultipleAPIs", + in: "Goose.v1alpha1.birds.com,Moose.v1alpha1.mammals.com", + want: APISet{ + opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, + opregistry.APIKey{Group: "mammals.com", Version: "v1alpha1", Kind: "Moose"}: {}, + }, + }, + { + name: "SingleGoodGVK/SingleBadGVK/SingleAPI", + in: "Goose.v1alpha1.birds.com,Moose.v1alpha1", + want: APISet{ + opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, + }, + }, + { + name: "MultipleGoodGVK/MultipleBadGVK/MultipleAPIs", + in: "Goose.v1alpha1.birds.com,Moose.v1alpha1,Goat,Egret.v1beta1.birds.com", + want: APISet{ + opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, + opregistry.APIKey{Group: "birds.com", Version: "v1beta1", Kind: "Egret"}: {}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.EqualValues(t, tt.want, GVKStringToProvidedAPISet(tt.in)) + }) + } +} +func TestAPIKeyToGVKString(t *testing.T) { + tests := []struct{ + name string + in opregistry.APIKey + want string + }{ + { + name: "EmptyAPIKey", + in: opregistry.APIKey{}, + want: "..", + }, + { + name: "BadAPIKey", + in: opregistry.APIKey{Group: "birds. ", Version: "-"}, + want: ".-.birds. ", + }, + { + name: "GoodAPIKey", + in: opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}, + want: "Goose.v1alpha1.birds.com", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.want, APIKeyToGVKString(tt.in)) + }) + } +} + +func TestAPISetString(t *testing.T) { + tests := []struct{ + name string + in APISet + want string + }{ + { + name: "EmptySet", + in: make(APISet), + want: "", + }, + { + name: "OneAPI", + in: APISet{ + opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, + }, + want: "Goose.v1alpha1.birds.com", + }, + { + name: "MutipleAPIs", + in: APISet{ + opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, + opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Egret"}: {}, + }, + want: "Egret.v1alpha1.birds.com,Goose.v1alpha1.birds.com", + }, + { + name: "MutipleAPIs/OneBad", + in: APISet{ + opregistry.APIKey{Group: "birds.com", Version: "v1alpha1"}: {}, + opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, + opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Egret"}: {}, + }, + want: ".v1alpha1.birds.com,Egret.v1alpha1.birds.com,Goose.v1alpha1.birds.com", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.want, tt.in.String()) + }) + } +} + +func TestAPISetUnion(t *testing.T) { + type input struct { + left APISet + right []APISet + } + tests := []struct{ + name string + in input + want APISet + }{ + { + name: "EmptyLeft/NilRight/EmptySet", + in: input{ + left: make(APISet), + right: nil, + }, + want: make(APISet), + }, + { + name: "EmptyLeft/OneEmptyRight/EmptySet", + in: input{ + left: make(APISet), + right: []APISet{ + {}, + }, + }, + want: make(APISet), + }, + { + name: "EmptyLeft/OneRight/OneFromRight", + in: input{ + left: make(APISet), + right: []APISet{ + { + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + }, + }, + }, + want: APISet{ + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + }, + }, + { + name: "OneLeft/EmptyRight/OneFromLeft", + in: input{ + left: APISet{ + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + }, + right: []APISet{ + {}, + }, + }, + want: APISet{ + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + }, + }, + { + name: "MultipleLeft/MultipleRight/AllFromLeftAndRight", + in: input{ + left: APISet{ + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + opregistry.APIKey{Group: "Egret", Version: "v1beta1", Kind: "birds.com"}: {}, + }, + right: []APISet{ + { + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + opregistry.APIKey{Group: "Egret", Version: "v1beta1", Kind: "birds.com"}: {}, + opregistry.APIKey{Group: "Crow", Version: "v1beta1", Kind: "birds.com"}: {}, + }, + { + // Empty APISet for good measure + }, + { + opregistry.APIKey{Group: "Moose", Version: "v1alpha1", Kind: "mammals.com"}: {}, + opregistry.APIKey{Group: "Cow", Version: "v1alpha1", Kind: "mammals.com"}: {}, + opregistry.APIKey{Group: "Egret", Version: "v1beta1", Kind: "birds.com"}: {}, + opregistry.APIKey{Group: "Crow", Version: "v1beta1", Kind: "birds.com"}: {}, + }, + { + opregistry.APIKey{Group: "Moose", Version: "v1alpha1", Kind: "mammals.com"}: {}, + opregistry.APIKey{Group: "Cow", Version: "v1alpha1", Kind: "mammals.com"}: {}, + opregistry.APIKey{Group: "Goat", Version: "v1beta1", Kind: "mammals.com"}: {}, + }, + }, + }, + want: APISet{ + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + opregistry.APIKey{Group: "Egret", Version: "v1beta1", Kind: "birds.com"}: {}, + opregistry.APIKey{Group: "Crow", Version: "v1beta1", Kind: "birds.com"}: {}, + opregistry.APIKey{Group: "Moose", Version: "v1alpha1", Kind: "mammals.com"}: {}, + opregistry.APIKey{Group: "Cow", Version: "v1alpha1", Kind: "mammals.com"}: {}, + opregistry.APIKey{Group: "Goat", Version: "v1beta1", Kind: "mammals.com"}: {}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.EqualValues(t, tt.want, tt.in.left.Union(tt.in.right...)) + }) + } +} + +func TestAPISetIntersection(t *testing.T) { + type input struct { + left APISet + right []APISet + } + tests := []struct{ + name string + in input + want APISet + }{ + { + name: "EmptyLeft/NilRight/EmptySet", + in: input{ + left: make(APISet), + right: nil, + }, + want: make(APISet), + }, + { + name: "EmptyLeft/OneEmptyRight/EmptySet", + in: input{ + left: make(APISet), + right: []APISet{ + {}, + }, + }, + want: make(APISet), + }, + { + name: "EmptyLeft/OneRight/EmptySet", + in: input{ + left: make(APISet), + right: []APISet{ + { + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + }, + }, + }, + want: make(APISet), + }, + { + name: "OneLeft/EmptyRight/NoIntersection", + in: input{ + left: APISet{ + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + }, + right: []APISet{ + {}, + }, + }, + want: make(APISet), + }, + { + name: "OneLeft/TwoRight/OneIntersection", + in: input{ + left: APISet{ + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + }, + right: []APISet{ + { + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + opregistry.APIKey{Group: "Moose", Version: "v1alpha1", Kind: "mammals.com"}: {}, + }, + }, + }, + want: APISet{ + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + }, + }, + { + name: "OneLeft/TwoRight/SingleSet/OneIntersection", + in: input{ + left: APISet{ + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + }, + right: []APISet{ + { + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + opregistry.APIKey{Group: "Moose", Version: "v1alpha1", Kind: "mammals.com"}: {}, + }, + }, + }, + want: APISet{ + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + }, + }, + { + name: "TwoLeft/OneRight/SingleSet/OneIntersection", + in: input{ + left: APISet{ + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + opregistry.APIKey{Group: "Moose", Version: "v1alpha1", Kind: "mammals.com"}: {}, + }, + right: []APISet{ + { + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + }, + }, + }, + want: APISet{ + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + }, + }, + { + name: "OneLeft/TwoRight/SeparateSets/OneIntersection", + in: input{ + left: APISet{ + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + }, + right: []APISet{ + { + opregistry.APIKey{Group: "Moose", Version: "v1alpha1", Kind: "mammals.com"}: {}, + }, + { + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + }, + }, + }, + want: APISet{ + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + }, + }, + { + name: "OneLeft/TwoRight/SeparateSets/NoIntersection", + in: input{ + left: APISet{ + opregistry.APIKey{Group: "Egret", Version: "v1alpha1", Kind: "birds.com"}: {}, + }, + right: []APISet{ + { + opregistry.APIKey{Group: "Moose", Version: "v1alpha1", Kind: "mammals.com"}: {}, + }, + { + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + }, + }, + }, + want: make(APISet), + }, + { + name: "MultipleLeft/MultipleRight/SeparateSets/SomeIntersection", + in: input{ + left: APISet{ + opregistry.APIKey{Group: "Egret", Version: "v1alpha1", Kind: "birds.com"}: {}, + opregistry.APIKey{Group: "Hippo", Version: "v1alpha1", Kind: "mammals.com"}: {}, + opregistry.APIKey{Group: "Moose", Version: "v1alpha1", Kind: "mammals.com"}: {}, + }, + right: []APISet{ + { + opregistry.APIKey{Group: "Hippo", Version: "v1alpha1", Kind: "mammals.com"}: {}, + }, + { + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + }, + { + opregistry.APIKey{Group: "Goat", Version: "v1alpha1", Kind: "mammals.com"}: {}, + }, + { + opregistry.APIKey{Group: "Moose", Version: "v1alpha1", Kind: "mammals.com"}: {}, + }, + }, + }, + want: APISet{ + opregistry.APIKey{Group: "Hippo", Version: "v1alpha1", Kind: "mammals.com"}: {}, + opregistry.APIKey{Group: "Moose", Version: "v1alpha1", Kind: "mammals.com"}: {}, + }, + }, + } + + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.EqualValues(t, tt.want, tt.in.left.Intersection(tt.in.right...)) + }) + } +} + +func TestAPISetDifference(t *testing.T) { + type input struct { + left APISet + right APISet + } + tests := []struct{ + name string + in input + want APISet + }{ + { + name: "EmptySet", + in: input{ + left: make(APISet), + right: make(APISet), + }, + want: make(APISet), + }, + { + name: "OneLeft/EmptyRight/LeftIsDifference", + in: input{ + left: APISet{ + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + }, + right: make(APISet), + }, + want: APISet{ + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + }, + }, + { + name: "EmptyLeft/OneRight/NoDifference", + in: input{ + left: make(APISet), + right: APISet{ + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + }, + }, + want: make(APISet), + }, + { + name: "OneLeft/OneRight/NoDifference", + in: input{ + left: APISet{ + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + }, + right: APISet{ + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + }, + }, + want: make(APISet), + }, + { + name: "MultipleLeft/MultipleRight/NoDifference", + in: input{ + left: APISet{ + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + opregistry.APIKey{Group: "Moose", Version: "v1alpha1", Kind: "mammals.com"}: {}, + }, + right: APISet{ + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + opregistry.APIKey{Group: "Moose", Version: "v1alpha1", Kind: "mammals.com"}: {}, + }, + }, + want: make(APISet), + }, + { + name: "MultipleLeft/MultipleRight/SingleDifference", + in: input{ + left: APISet{ + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + opregistry.APIKey{Group: "Moose", Version: "v1alpha1", Kind: "mammals.com"}: {}, + opregistry.APIKey{Group: "Goat", Version: "v1alpha1", Kind: "mammals.com"}: {}, + }, + right: APISet{ + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + opregistry.APIKey{Group: "Moose", Version: "v1alpha1", Kind: "mammals.com"}: {}, + }, + }, + want: APISet{ + opregistry.APIKey{Group: "Goat", Version: "v1alpha1", Kind: "mammals.com"}: {}, + }, + }, + { + name: "MultipleLeft/MultipleRight/SomeDifference", + in: input{ + left: APISet{ + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + opregistry.APIKey{Group: "Moose", Version: "v1alpha1", Kind: "mammals.com"}: {}, + opregistry.APIKey{Group: "Goat", Version: "v1alpha1", Kind: "mammals.com"}: {}, + }, + right: APISet{ + opregistry.APIKey{Group: "Moose", Version: "v1alpha1", Kind: "mammals.com"}: {}, + opregistry.APIKey{Group: "Gopher", Version: "v1alpha2", Kind: "mammals.com"}: {}, + }, + }, + want: APISet{ + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + opregistry.APIKey{Group: "Goat", Version: "v1alpha1", Kind: "mammals.com"}: {}, + }, + }, + { + name: "MultipleLeft/MultipleRight/AllLeftDifference", + in: input{ + left: APISet{ + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + opregistry.APIKey{Group: "Moose", Version: "v1alpha1", Kind: "mammals.com"}: {}, + opregistry.APIKey{Group: "Goat", Version: "v1alpha1", Kind: "mammals.com"}: {}, + }, + right: APISet{ + opregistry.APIKey{Group: "Giraffe", Version: "v1alpha1", Kind: "mammals.com"}: {}, + opregistry.APIKey{Group: "Gopher", Version: "v1alpha2", Kind: "mammals.com"}: {}, + opregistry.APIKey{Group: "Bison", Version: "v1beta1", Kind: "mammals.com"}: {}, + }, + }, + want: APISet{ + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + opregistry.APIKey{Group: "Moose", Version: "v1alpha1", Kind: "mammals.com"}: {}, + opregistry.APIKey{Group: "Goat", Version: "v1alpha1", Kind: "mammals.com"}: {}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T){ + require.EqualValues(t, tt.want, tt.in.left.Difference(tt.in.right)) + }) + } +} + +func TestAPISetIsSubset(t *testing.T) { + type input struct { + left APISet + right APISet + } + tests := []struct{ + name string + in input + want bool + }{ + { + name: "EmptySet", + in: input{ + left: make(APISet), + right: make(APISet), + }, + want: true, + }, + { + name: "Same", + in: input{ + left: APISet{ + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + }, + right:APISet{ + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + }, + }, + want: true, + }, + { + name: "IsSubset", + in: input{ + left: APISet{ + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + }, + right:APISet{ + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + opregistry.APIKey{Group: "Moose", Version: "v1alpha1", Kind: "mammals.com"}: {}, + }, + }, + want: true, + }, + { + name: "NotSubset", + in: input{ + left: APISet{ + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + opregistry.APIKey{Group: "Moose", Version: "v1alpha1", Kind: "mammals.com"}: {}, + }, + right:APISet{ + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + }, + }, + want: false, + }, + { + name: "NotSubset/EmptyRight", + in: input{ + left: APISet{ + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + opregistry.APIKey{Group: "Moose", Version: "v1alpha1", Kind: "mammals.com"}: {}, + }, + right: make(APISet), + }, + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.want, tt.in.left.IsSubset(tt.in.right)) + }) + } +} + +func TestStripPlural(t *testing.T) { + tests := []struct{ + name string + in APISet + want APISet + }{ + { + name: "EmptySet", + in: make(APISet), + want: make(APISet), + }, + { + name: "NilSet", + in: nil, + want: make(APISet), + }, + { + name: "OnePluralToRemove", + in: APISet{ + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com", Plural: "Geese"}: {}, + }, + want: APISet{ + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + }, + }, + { + name: "MultiplePluralsToRemove", + in: APISet{ + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com", Plural: "Geese"}: {}, + opregistry.APIKey{Group: "Moose", Version: "v1alpha1", Kind: "mammals.com", Plural: "Moose"}: {}, + }, + want: APISet{ + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + opregistry.APIKey{Group: "Moose", Version: "v1alpha1", Kind: "mammals.com"}: {}, + }, + }, + { + name: "NoPluralsToRemove", + in: APISet{ + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + opregistry.APIKey{Group: "Moose", Version: "v1alpha1", Kind: "mammals.com"}: {}, + }, + want: APISet{ + opregistry.APIKey{Group: "Goose", Version: "v1alpha1", Kind: "birds.com"}: {}, + opregistry.APIKey{Group: "Moose", Version: "v1alpha1", Kind: "mammals.com"}: {}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T){ + require.EqualValues(t, tt.want, tt.in.StripPlural()) + }) + } +} + func TestCatalogKey_String(t *testing.T) { type fields struct { Name string diff --git a/pkg/controller/registry/resolver/querier.go b/pkg/controller/registry/resolver/querier.go index 57f1bd3b95..26b924ad7b 100644 --- a/pkg/controller/registry/resolver/querier.go +++ b/pkg/controller/registry/resolver/querier.go @@ -56,42 +56,6 @@ func (q *NamespaceSourceQuerier) FindProvider(api opregistry.APIKey) (*opregistr return nil, nil, fmt.Errorf("%s not provided by a package in any CatalogSource", api) } -func (q *NamespaceSourceQuerier) FindPackage(pkgName, channelName, csvName string, initialSource CatalogKey) (*opregistry.Bundle, *CatalogKey, error) { - if initialSource.Name != "" && initialSource.Namespace != "" { - source, ok := q.sources[initialSource] - if !ok { - return nil, nil, fmt.Errorf("CatalogSource %s not found", initialSource) - } - - var bundle *opregistry.Bundle - var err error - if csvName != "" { - bundle, err = source.GetBundle(context.TODO(), pkgName, channelName, csvName) - } else { - bundle, err = source.GetBundleInPackageChannel(context.TODO(), pkgName, channelName) - } - - if err != nil { - return nil, nil, err - } - return bundle, &initialSource, nil - } - - for key, source := range q.sources { - var bundle *opregistry.Bundle - var err error - if csvName != "" { - bundle, err = source.GetBundle(context.TODO(), pkgName, channelName, csvName) - } else { - bundle, err = source.GetBundleInPackageChannel(context.TODO(), pkgName, channelName) - } - if err == nil { - return bundle, &key, nil - } - } - return nil, nil, fmt.Errorf("%s/%s not found in any available CatalogSource", pkgName, channelName) -} - func (q *NamespaceSourceQuerier) FindBundle(pkgName, channelName, bundleName string, initialSource CatalogKey) (*opregistry.Bundle, *CatalogKey, error) { if initialSource.Name != "" && initialSource.Namespace != "" { source, ok := q.sources[initialSource] diff --git a/pkg/fakes/fake_api_intersection_reconciler.go b/pkg/fakes/fake_api_intersection_reconciler.go new file mode 100644 index 0000000000..f67ba401da --- /dev/null +++ b/pkg/fakes/fake_api_intersection_reconciler.go @@ -0,0 +1,114 @@ +// Code generated by counterfeiter. DO NOT EDIT. +package fakes + +import ( + sync "sync" + + resolver "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver" +) + +type FakeAPIIntersectionReconciler struct { + ReconcileStub func(resolver.APISet, resolver.OperatorGroupSurface, ...resolver.OperatorGroupSurface) resolver.APIReconciliationResult + reconcileMutex sync.RWMutex + reconcileArgsForCall []struct { + arg1 resolver.APISet + arg2 resolver.OperatorGroupSurface + arg3 []resolver.OperatorGroupSurface + } + reconcileReturns struct { + result1 resolver.APIReconciliationResult + } + reconcileReturnsOnCall map[int]struct { + result1 resolver.APIReconciliationResult + } + invocations map[string][][]interface{} + invocationsMutex sync.RWMutex +} + +func (fake *FakeAPIIntersectionReconciler) Reconcile(arg1 resolver.APISet, arg2 resolver.OperatorGroupSurface, arg3 ...resolver.OperatorGroupSurface) resolver.APIReconciliationResult { + fake.reconcileMutex.Lock() + ret, specificReturn := fake.reconcileReturnsOnCall[len(fake.reconcileArgsForCall)] + fake.reconcileArgsForCall = append(fake.reconcileArgsForCall, struct { + arg1 resolver.APISet + arg2 resolver.OperatorGroupSurface + arg3 []resolver.OperatorGroupSurface + }{arg1, arg2, arg3}) + fake.recordInvocation("Reconcile", []interface{}{arg1, arg2, arg3}) + fake.reconcileMutex.Unlock() + if fake.ReconcileStub != nil { + return fake.ReconcileStub(arg1, arg2, arg3...) + } + if specificReturn { + return ret.result1 + } + fakeReturns := fake.reconcileReturns + return fakeReturns.result1 +} + +func (fake *FakeAPIIntersectionReconciler) ReconcileCallCount() int { + fake.reconcileMutex.RLock() + defer fake.reconcileMutex.RUnlock() + return len(fake.reconcileArgsForCall) +} + +func (fake *FakeAPIIntersectionReconciler) ReconcileCalls(stub func(resolver.APISet, resolver.OperatorGroupSurface, ...resolver.OperatorGroupSurface) resolver.APIReconciliationResult) { + fake.reconcileMutex.Lock() + defer fake.reconcileMutex.Unlock() + fake.ReconcileStub = stub +} + +func (fake *FakeAPIIntersectionReconciler) ReconcileArgsForCall(i int) (resolver.APISet, resolver.OperatorGroupSurface, []resolver.OperatorGroupSurface) { + fake.reconcileMutex.RLock() + defer fake.reconcileMutex.RUnlock() + argsForCall := fake.reconcileArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 +} + +func (fake *FakeAPIIntersectionReconciler) ReconcileReturns(result1 resolver.APIReconciliationResult) { + fake.reconcileMutex.Lock() + defer fake.reconcileMutex.Unlock() + fake.ReconcileStub = nil + fake.reconcileReturns = struct { + result1 resolver.APIReconciliationResult + }{result1} +} + +func (fake *FakeAPIIntersectionReconciler) ReconcileReturnsOnCall(i int, result1 resolver.APIReconciliationResult) { + fake.reconcileMutex.Lock() + defer fake.reconcileMutex.Unlock() + fake.ReconcileStub = nil + if fake.reconcileReturnsOnCall == nil { + fake.reconcileReturnsOnCall = make(map[int]struct { + result1 resolver.APIReconciliationResult + }) + } + fake.reconcileReturnsOnCall[i] = struct { + result1 resolver.APIReconciliationResult + }{result1} +} + +func (fake *FakeAPIIntersectionReconciler) Invocations() map[string][][]interface{} { + fake.invocationsMutex.RLock() + defer fake.invocationsMutex.RUnlock() + fake.reconcileMutex.RLock() + defer fake.reconcileMutex.RUnlock() + copiedInvocations := map[string][][]interface{}{} + for key, value := range fake.invocations { + copiedInvocations[key] = value + } + return copiedInvocations +} + +func (fake *FakeAPIIntersectionReconciler) 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 _ resolver.APIIntersectionReconciler = new(FakeAPIIntersectionReconciler) diff --git a/pkg/lib/operatorlister/operatorgroup.go b/pkg/lib/operatorlister/operatorgroup.go index 5b376a574a..0adc93f4dc 100644 --- a/pkg/lib/operatorlister/operatorgroup.go +++ b/pkg/lib/operatorlister/operatorgroup.go @@ -4,11 +4,12 @@ import ( "fmt" "sync" - "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha2" - listers "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/listers/operators/v1alpha2" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/types" + + "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha2" + listers "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/listers/operators/v1alpha2" ) type UnionOperatorGroupLister struct { diff --git a/test/e2e/csv_e2e_test.go b/test/e2e/csv_e2e_test.go index fb06571250..055a1db317 100644 --- a/test/e2e/csv_e2e_test.go +++ b/test/e2e/csv_e2e_test.go @@ -196,11 +196,22 @@ func buildCSVConditionChecker(phases ...v1alpha1.ClusterServiceVersionPhase) csv } } +func buildCSVReasonChecker(reasons ...v1alpha1.ConditionReason) csvConditionChecker { + return func(csv *v1alpha1.ClusterServiceVersion) bool { + conditionMet := false + for _, reason := range reasons { + conditionMet = conditionMet || csv.Status.Reason == reason + } + return conditionMet + } +} + var csvPendingChecker = buildCSVConditionChecker(v1alpha1.CSVPhasePending) var csvSucceededChecker = buildCSVConditionChecker(v1alpha1.CSVPhaseSucceeded) var csvReplacingChecker = buildCSVConditionChecker(v1alpha1.CSVPhaseReplacing, v1alpha1.CSVPhaseDeleting) var csvFailedChecker = buildCSVConditionChecker(v1alpha1.CSVPhaseFailed) var csvAnyChecker = buildCSVConditionChecker(v1alpha1.CSVPhasePending, v1alpha1.CSVPhaseSucceeded, v1alpha1.CSVPhaseReplacing, v1alpha1.CSVPhaseDeleting, v1alpha1.CSVPhaseFailed) +var csvCopiedChecker = buildCSVReasonChecker(v1alpha1.CSVReasonCopied) func fetchCSV(t *testing.T, c versioned.Interface, name, namespace string, checker csvConditionChecker) (*v1alpha1.ClusterServiceVersion, error) { var fetched *v1alpha1.ClusterServiceVersion diff --git a/test/e2e/operator_groups_e2e_test.go b/test/e2e/operator_groups_e2e_test.go index 887d3e9c38..288f65f0b1 100644 --- a/test/e2e/operator_groups_e2e_test.go +++ b/test/e2e/operator_groups_e2e_test.go @@ -24,6 +24,7 @@ import ( "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha2" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install" "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient" + "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry" ) func DeploymentComplete(deployment *appsv1.Deployment, newStatus *appsv1.DeploymentStatus) bool { @@ -100,6 +101,21 @@ func checkOperatorGroupAnnotations(obj metav1.Object, op *v1alpha2.OperatorGroup return nil } +func newOperatorGroup(namespace, name string, annotations map[string]string, selector metav1.LabelSelector, targetNamespaces []string, static bool) *v1alpha2.OperatorGroup { + return &v1alpha2.OperatorGroup{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: name, + Annotations: annotations, + }, + Spec: v1alpha2.OperatorGroupSpec{ + TargetNamespaces: targetNamespaces, + Selector: selector, + StaticProvidedAPIs: static, + }, + } +} + func TestOperatorGroup(t *testing.T) { // Create namespace with specific label // Create CRD @@ -431,5 +447,457 @@ func TestOperatorGroup(t *testing.T) { return err }) require.NoError(t, err) +} + + +func TestOperatorGroupIntersection(t *testing.T) { + // Generate namespaceA + // Generate namespaceB + // Generate namespaceC + // Generate namespaceD + // Generate namespaceE + // Generate operatorGroupD in namespaceD that selects namespace D and E + // Generate csvD in namespaceD + // Wait for csvD to be successful + // Wait for csvD to have a CSV with copied status in namespace D + // Wait for operatorGroupD to have providedAPI annotation with crdD's Kind.version.group + // Generate operatorGroupA in namespaceA that selects AllNamespaces + // Generate csvD in namespaceA + // Wait for csvD to fail with status "InterOperatorGroupOwnerConflict" + // Ensure operatorGroupA's providedAPIs are empty + // Ensure csvD in namespaceD is still successful + // Generate csvA in namespaceA that owns crdA + // Wait for csvA to be successful + // Wait for operatorGroupA to have providedAPI annotation with crdA's Kind.version.group in its providedAPIs annotation + // Wait for csvA to have a CSV with copied status in namespace C + // Generate operatorGroupB in namespaceB that selects namespace C + // Generate csvB in namespaceB that owns crdA + // Wait for csvB to fail with status "InterOperatorGroupOwnerConflict" + // Delete csvA + // Wait for crdA's Kind.version.group to be removed from operatorGroupA's providedAPIs annotation + // Ensure csvA's deployments are deleted + // Wait for csvB to be successful + // Wait for operatorGroupB to have providedAPI annotation with crdB's Kind.version.group + // Wait for csvB to have a CSV with a copied status in namespace C + + // Create a catalog for csvA, csvB, and csvD + pkgA := genName("a") + pkgB := genName("b") + pkgD := genName("d") + pkgAStable := pkgA + "-stable" + pkgBStable := pkgB + "-stable" + pkgDStable := pkgD + "-stable" + stableChannel := "stable" + strategyA := newNginxInstallStrategy(pkgAStable, nil, nil) + strategyB := newNginxInstallStrategy(pkgBStable, nil, nil) + strategyD := newNginxInstallStrategy(pkgDStable, nil, nil) + crdA := newCRD(genName(pkgA)) + crdB := newCRD(genName(pkgB)) + crdD := newCRD(genName(pkgD)) + kvgA := fmt.Sprintf("%s.%s.%s", crdA.Spec.Names.Kind, crdA.Spec.Version, crdA.Spec.Group) + kvgB := fmt.Sprintf("%s.%s.%s", crdB.Spec.Names.Kind, crdB.Spec.Version, crdB.Spec.Group) + kvgD := fmt.Sprintf("%s.%s.%s", crdD.Spec.Names.Kind, crdD.Spec.Version, crdD.Spec.Group) + csvA := newCSV(pkgAStable, testNamespace, "", *semver.New("0.1.0"), []apiextensions.CustomResourceDefinition{crdA}, nil, strategyA) + csvB := newCSV(pkgBStable, testNamespace, "", *semver.New("0.1.0"), []apiextensions.CustomResourceDefinition{crdA, crdB}, nil, strategyB) + csvD := newCSV(pkgDStable, testNamespace, "", *semver.New("0.1.0"), []apiextensions.CustomResourceDefinition{crdD}, nil, strategyD) + + // Create namespaces + nsA, nsB, nsC, nsD, nsE := genName("a"), genName("b"), genName("c"), genName("d"), genName("e") + c := newKubeClient(t) + crc := newCRClient(t) + for _, ns := range []string{nsA, nsB, nsC, nsD, nsE} { + namespace := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: ns, + }, + } + _, err := c.KubernetesInterface().CoreV1().Namespaces().Create(namespace) + require.NoError(t, err) + defer func(name string) { + require.NoError(t, c.KubernetesInterface().CoreV1().Namespaces().Delete(name, &metav1.DeleteOptions{})) + }(ns) + } + + // Create the initial catalogsources + manifests := []registry.PackageManifest{ + { + PackageName: pkgA, + Channels: []registry.PackageChannel{ + {Name: stableChannel, CurrentCSVName: pkgAStable}, + }, + DefaultChannelName: stableChannel, + }, + { + PackageName: pkgB, + Channels: []registry.PackageChannel{ + {Name: stableChannel, CurrentCSVName: pkgBStable}, + }, + DefaultChannelName: stableChannel, + }, + { + PackageName: pkgD, + Channels: []registry.PackageChannel{ + {Name: stableChannel, CurrentCSVName: pkgDStable}, + }, + DefaultChannelName: stableChannel, + }, + } + + catalog := genName("catalog-") + _, cleanupCatalogSource := createInternalCatalogSource(t, c, crc, catalog, nsA, manifests, []apiextensions.CustomResourceDefinition{crdA, crdD, crdB}, []v1alpha1.ClusterServiceVersion{csvA, csvB, csvD}) + defer cleanupCatalogSource() + _, err := fetchCatalogSource(t, crc, catalog, nsA, catalogSourceRegistryPodSynced) + require.NoError(t, err) + _, cleanupCatalogSource = createInternalCatalogSource(t, c, crc, catalog, nsB, manifests, []apiextensions.CustomResourceDefinition{crdA, crdD, crdB}, []v1alpha1.ClusterServiceVersion{csvA, csvB, csvD}) + defer cleanupCatalogSource() + _, err = fetchCatalogSource(t, crc, catalog, nsB, catalogSourceRegistryPodSynced) + require.NoError(t, err) + _, cleanupCatalogSource = createInternalCatalogSource(t, c, crc, catalog, nsD, manifests, []apiextensions.CustomResourceDefinition{crdA, crdD, crdB}, []v1alpha1.ClusterServiceVersion{csvA, csvB, csvD}) + defer cleanupCatalogSource() + _, err = fetchCatalogSource(t, crc, catalog, nsD, catalogSourceRegistryPodSynced) + require.NoError(t, err) + + // Create operatorgroups + groupA := newOperatorGroup(nsA, genName("a"), nil, metav1.LabelSelector{}, nil, false) + groupB := newOperatorGroup(nsB, genName("b"), nil, metav1.LabelSelector{}, []string{nsC}, false) + groupD := newOperatorGroup(nsD, genName("d"), nil, metav1.LabelSelector{}, []string{nsD, nsE}, false) + for _, group := range []*v1alpha2.OperatorGroup{groupA, groupB, groupD} { + _, err := crc.OperatorsV1alpha2().OperatorGroups(group.GetNamespace()).Create(group) + require.NoError(t, err) + defer func(namespace, name string) { + require.NoError(t, crc.OperatorsV1alpha2().OperatorGroups(namespace).Delete(name, &metav1.DeleteOptions{})) + }(group.GetNamespace(), group.GetName()) + } + + // Create subscription for csvD in namespaceD + subDName := genName("d") + cleanupSubD := createSubscriptionForCatalog(t, crc, nsD, subDName, catalog, pkgD, stableChannel, pkgDStable, v1alpha1.ApprovalAutomatic) + defer cleanupSubD() + subD, err := fetchSubscription(t, crc, nsD, subDName, subscriptionHasInstallPlanChecker) + require.NoError(t, err) + require.NotNil(t, subD) + + // Await csvD's success + _, err = awaitCSV(t, crc, nsD, csvD.GetName(), csvSucceededChecker) + require.NoError(t, err) + + // Await csvD's copy in namespaceE + _, err = awaitCSV(t, crc, nsE, csvD.GetName(), csvCopiedChecker) + require.NoError(t, err) + + // Await annotation on groupD + q := func() (metav1.ObjectMeta, error) { + g, err := crc.OperatorsV1alpha2().OperatorGroups(nsD).Get(groupD.GetName(), metav1.GetOptions{}) + return g.ObjectMeta, err + } + require.NoError(t, awaitAnnotations(t, q, map[string]string{v1alpha2.OperatorGroupProvidedAPIsAnnotationKey: kvgD})) + + // Create subscription for csvD2 in namespaceA + subD2Name := genName("d") + cleanupSubD2 := createSubscriptionForCatalog(t, crc, nsA, subD2Name, catalog, pkgD, stableChannel, pkgDStable, v1alpha1.ApprovalAutomatic) + defer cleanupSubD2() + subD2, err := fetchSubscription(t, crc, nsA, subD2Name, subscriptionHasInstallPlanChecker) + require.NoError(t, err) + require.NotNil(t, subD2) + + // Await csvD2's failure + csvD2, err := awaitCSV(t, crc, nsA, csvD.GetName(), csvFailedChecker) + require.NoError(t, err) + require.Equal(t, v1alpha1.CSVReasonInterOperatorGroupOwnerConflict, csvD2.Status.Reason) + + // Ensure groupA's annotations are blank + q = func() (metav1.ObjectMeta, error) { + g, err := crc.OperatorsV1alpha2().OperatorGroups(nsA).Get(groupA.GetName(), metav1.GetOptions{}) + return g.ObjectMeta, err + } + require.NoError(t, awaitAnnotations(t, q, map[string]string{})) + + // Ensure csvD is still successful + _, err = awaitCSV(t, crc, nsD, csvD.GetName(), csvSucceededChecker) + require.NoError(t, err) + + // Create subscription for csvA in namespaceA + subAName := genName("a") + cleanupSubA := createSubscriptionForCatalog(t, crc, nsA, subAName, catalog, pkgA, stableChannel, pkgAStable, v1alpha1.ApprovalAutomatic) + defer cleanupSubA() + subA, err := fetchSubscription(t, crc, nsA, subAName, subscriptionHasInstallPlanChecker) + require.NoError(t, err) + require.NotNil(t, subA) + + // Await csvA's success + _, err = awaitCSV(t, crc, nsA, csvA.GetName(), csvSucceededChecker) + require.NoError(t, err) + + // Await annotation on groupA + q = func() (metav1.ObjectMeta, error) { + g, err := crc.OperatorsV1alpha2().OperatorGroups(nsA).Get(groupA.GetName(), metav1.GetOptions{}) + return g.ObjectMeta, err + } + require.NoError(t, awaitAnnotations(t, q, map[string]string{v1alpha2.OperatorGroupProvidedAPIsAnnotationKey: kvgA})) + + // Await csvA's copy in namespaceC + _, err = awaitCSV(t, crc, nsC, csvA.GetName(), csvCopiedChecker) + require.NoError(t, err) + + // Create subscription for csvB in namespaceB + subBName := genName("b") + cleanupSubB := createSubscriptionForCatalog(t, crc, nsB, subBName, catalog, pkgB, stableChannel, pkgBStable, v1alpha1.ApprovalAutomatic) + defer cleanupSubB() + subB, err := fetchSubscription(t, crc, nsB, subBName, subscriptionHasInstallPlanChecker) + require.NoError(t, err) + require.NotNil(t, subB) + + // Await csvB's failure + fetchedB, err := awaitCSV(t, crc, nsB, csvB.GetName(), csvFailedChecker) + require.NoError(t, err) + require.Equal(t, v1alpha1.CSVReasonInterOperatorGroupOwnerConflict, fetchedB.Status.Reason) + + // Ensure no annotation on groupB + q = func() (metav1.ObjectMeta, error) { + g, err := crc.OperatorsV1alpha2().OperatorGroups(nsB).Get(groupB.GetName(), metav1.GetOptions{}) + return g.ObjectMeta, err + } + require.NoError(t, awaitAnnotations(t, q, map[string]string{})) + + // Delete csvA + require.NoError(t, crc.OperatorsV1alpha1().ClusterServiceVersions(nsA).Delete(csvA.GetName(), &metav1.DeleteOptions{})) + + // Ensure annotations are removed from groupA + q = func() (metav1.ObjectMeta, error) { + g, err := crc.OperatorsV1alpha2().OperatorGroups(nsA).Get(groupA.GetName(), metav1.GetOptions{}) + return g.ObjectMeta, err + } + require.NoError(t, awaitAnnotations(t, q, map[string]string{v1alpha2.OperatorGroupProvidedAPIsAnnotationKey: ""})) + + // Ensure csvA's deployment is deleted + require.NoError(t, waitForDeploymentToDelete(t, c, pkgAStable)) + // Await csvB's success + _, err = awaitCSV(t, crc, nsB, csvB.GetName(), csvSucceededChecker) + require.NoError(t, err) + + // Await csvB's copy in namespace C + _, err = awaitCSV(t, crc, nsC, csvB.GetName(), csvCopiedChecker) + require.NoError(t, err) + + // Ensure annotations exist on group B + q = func() (metav1.ObjectMeta, error) { + g, err := crc.OperatorsV1alpha2().OperatorGroups(nsB).Get(groupB.GetName(), metav1.GetOptions{}) + return g.ObjectMeta, err + } + require.NoError(t, awaitAnnotations(t, q, map[string]string{v1alpha2.OperatorGroupProvidedAPIsAnnotationKey: strings.Join([]string{kvgA, kvgB}, ",")})) } + +func TestStaticProviderOperatorGroup(t *testing.T) { + // Generate namespaceA + // Generate namespaceB + // Generate namespaceC + // Generate namespaceD + // Create static operatorGroupA in namespaceA that targets namespaceD with providedAPIs annotation containing KindA.version.group + // Create operatorGroupB in namespaceB that targets all namespaces + // Create operatorGroupC in namespaceC that targets namespaceC + // Create csvA in namespaceB that provides KindA.version.group + // Wait for csvA in namespaceB to fail + // Ensure no providedAPI annotations on operatorGroupB + // Ensure providedAPI annotations are unchanged on operatorGroupA + // Create csvA in namespaceC + // Wait for csvA in namespaceC to succeed + // Ensure KindA.version.group providedAPI annotation on operatorGroupC + // Create csvB in namespaceB that provides KindB.version.group + // Wait for csvB to succeed + // Wait for csvB to be copied to namespaceA, namespaceC, and namespaceD + // Wait for KindB.version.group to exist in operatorGroupB's providedAPIs annotation + // Add namespaceD to operatorGroupC's targetNamespaces + // Wait for csvA in namespaceC to FAIL with status "InterOperatorGroupOwnerConflict" + // Wait for KindA.version.group providedAPI annotation to be removed from operatorGroupC's providedAPIs annotation + // Ensure KindA.version.group providedAPI annotation on operatorGroupA + + // Create a catalog for csvA, csvB + pkgA := genName("a") + pkgB := genName("b") + pkgAStable := pkgA + "-stable" + pkgBStable := pkgB + "-stable" + stableChannel := "stable" + strategyA := newNginxInstallStrategy(pkgAStable, nil, nil) + strategyB := newNginxInstallStrategy(pkgBStable, nil, nil) + crdA := newCRD(genName(pkgA)) + crdB := newCRD(genName(pkgB)) + kvgA := fmt.Sprintf("%s.%s.%s", crdA.Spec.Names.Kind, crdA.Spec.Version, crdA.Spec.Group) + kvgB := fmt.Sprintf("%s.%s.%s", crdB.Spec.Names.Kind, crdB.Spec.Version, crdB.Spec.Group) + csvA := newCSV(pkgAStable, testNamespace, "", *semver.New("0.1.0"), []apiextensions.CustomResourceDefinition{crdA}, nil, strategyA) + csvB := newCSV(pkgBStable, testNamespace, "", *semver.New("0.1.0"), []apiextensions.CustomResourceDefinition{crdB}, nil, strategyB) + + // Create namespaces + nsA, nsB, nsC, nsD := genName("a"), genName("b"), genName("c"), genName("d") + c := newKubeClient(t) + crc := newCRClient(t) + for _, ns := range []string{nsA, nsB, nsC, nsD} { + namespace := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: ns, + }, + } + _, err := c.KubernetesInterface().CoreV1().Namespaces().Create(namespace) + require.NoError(t, err) + defer func(name string) { + require.NoError(t, c.KubernetesInterface().CoreV1().Namespaces().Delete(name, &metav1.DeleteOptions{})) + }(ns) + } + + // Create the initial catalogsources + manifests := []registry.PackageManifest{ + { + PackageName: pkgA, + Channels: []registry.PackageChannel{ + {Name: stableChannel, CurrentCSVName: pkgAStable}, + }, + DefaultChannelName: stableChannel, + }, + { + PackageName: pkgB, + Channels: []registry.PackageChannel{ + {Name: stableChannel, CurrentCSVName: pkgBStable}, + }, + DefaultChannelName: stableChannel, + }, + } + + // Create catalog in namespaceB and namespaceC + catalog := genName("catalog-") + _, cleanupCatalogSource := createInternalCatalogSource(t, c, crc, catalog, nsB, manifests, []apiextensions.CustomResourceDefinition{crdA, crdB}, []v1alpha1.ClusterServiceVersion{csvA, csvB}) + defer cleanupCatalogSource() + _, err := fetchCatalogSource(t, crc, catalog, nsB, catalogSourceRegistryPodSynced) + require.NoError(t, err) + _, cleanupCatalogSource = createInternalCatalogSource(t, c, crc, catalog, nsC, manifests, []apiextensions.CustomResourceDefinition{crdA, crdB}, []v1alpha1.ClusterServiceVersion{csvA, csvB}) + defer cleanupCatalogSource() + _, err = fetchCatalogSource(t, crc, catalog, nsC, catalogSourceRegistryPodSynced) + require.NoError(t, err) + + // Create OperatorGroups + groupA := newOperatorGroup(nsA, genName("a"), map[string]string{v1alpha2.OperatorGroupProvidedAPIsAnnotationKey: kvgA}, metav1.LabelSelector{}, []string{nsD}, true) + groupB := newOperatorGroup(nsB, genName("b"), nil, metav1.LabelSelector{}, nil, false) + groupC := newOperatorGroup(nsC, genName("d"), nil, metav1.LabelSelector{}, []string{nsC}, false) + for _, group := range []*v1alpha2.OperatorGroup{groupA, groupB, groupC} { + _, err := crc.OperatorsV1alpha2().OperatorGroups(group.GetNamespace()).Create(group) + require.NoError(t, err) + defer func(namespace, name string) { + require.NoError(t, crc.OperatorsV1alpha2().OperatorGroups(namespace).Delete(name, &metav1.DeleteOptions{})) + }(group.GetNamespace(), group.GetName()) + } + + // Create subscription for csvA in namespaceB + subAName := genName("a") + cleanupSubA := createSubscriptionForCatalog(t, crc, nsB, subAName, catalog, pkgA, stableChannel, pkgAStable, v1alpha1.ApprovalAutomatic) + defer cleanupSubA() + subA, err := fetchSubscription(t, crc, nsB, subAName, subscriptionHasInstallPlanChecker) + require.NoError(t, err) + require.NotNil(t, subA) + + // Await csvA's failure + fetchedCSVA, err := awaitCSV(t, crc, nsB, csvA.GetName(), csvFailedChecker) + require.NoError(t, err) + require.Equal(t, v1alpha1.CSVReasonInterOperatorGroupOwnerConflict, fetchedCSVA.Status.Reason) + + // Ensure operatorGroupB doesn't have providedAPI annotation + q := func() (metav1.ObjectMeta, error) { + g, err := crc.OperatorsV1alpha2().OperatorGroups(nsB).Get(groupB.GetName(), metav1.GetOptions{}) + return g.ObjectMeta, err + } + require.NoError(t, awaitAnnotations(t, q, map[string]string{})) + + // Ensure operatorGroupA still has KindA.version.group in its providedAPIs annotation + q = func() (metav1.ObjectMeta, error) { + g, err := crc.OperatorsV1alpha2().OperatorGroups(nsA).Get(groupA.GetName(), metav1.GetOptions{}) + return g.ObjectMeta, err + } + require.NoError(t, awaitAnnotations(t, q, map[string]string{v1alpha2.OperatorGroupProvidedAPIsAnnotationKey: kvgA})) + + // Create subscription for csvA in namespaceC + cleanupSubAC := createSubscriptionForCatalog(t, crc, nsC, subAName, catalog, pkgA, stableChannel, pkgAStable, v1alpha1.ApprovalAutomatic) + defer cleanupSubAC() + subAC, err := fetchSubscription(t, crc, nsC, subAName, subscriptionHasInstallPlanChecker) + require.NoError(t, err) + require.NotNil(t, subAC) + + // Await csvA's success + _, err = awaitCSV(t, crc, nsC, csvA.GetName(), csvSucceededChecker) + require.NoError(t, err) + + // Ensure operatorGroupC has KindA.version.group in its providedAPIs annotation + q = func() (metav1.ObjectMeta, error) { + g, err := crc.OperatorsV1alpha2().OperatorGroups(nsC).Get(groupC.GetName(), metav1.GetOptions{}) + return g.ObjectMeta, err + } + require.NoError(t, awaitAnnotations(t, q, map[string]string{v1alpha2.OperatorGroupProvidedAPIsAnnotationKey: kvgA})) + + // Ensure operatorGroupA still has KindA.version.group in its providedAPIs annotation + q = func() (metav1.ObjectMeta, error) { + g, err := crc.OperatorsV1alpha2().OperatorGroups(nsA).Get(groupA.GetName(), metav1.GetOptions{}) + return g.ObjectMeta, err + } + require.NoError(t, awaitAnnotations(t, q, map[string]string{v1alpha2.OperatorGroupProvidedAPIsAnnotationKey: kvgA})) + + // Create subscription for csvB in namespaceB + subBName := genName("b") + cleanupSubB := createSubscriptionForCatalog(t, crc, nsB, subBName, catalog, pkgB, stableChannel, pkgBStable, v1alpha1.ApprovalAutomatic) + defer cleanupSubB() + subB, err := fetchSubscription(t, crc, nsB, subBName, subscriptionHasInstallPlanChecker) + require.NoError(t, err) + require.NotNil(t, subB) + + // Await csvB's success + _, err = awaitCSV(t, crc, nsB, csvB.GetName(), csvSucceededChecker) + require.NoError(t, err) + + // Await copied csvBs + _, err = awaitCSV(t, crc, nsA, csvB.GetName(), csvCopiedChecker) + require.NoError(t, err) + _, err = awaitCSV(t, crc, nsC, csvB.GetName(), csvCopiedChecker) + require.NoError(t, err) + _, err = awaitCSV(t, crc, nsD, csvB.GetName(), csvCopiedChecker) + require.NoError(t, err) + + // Ensure operatorGroupB has KindB.version.group in its providedAPIs annotation + q = func() (metav1.ObjectMeta, error) { + g, err := crc.OperatorsV1alpha2().OperatorGroups(nsB).Get(groupB.GetName(), metav1.GetOptions{}) + return g.ObjectMeta, err + } + require.NoError(t, awaitAnnotations(t, q, map[string]string{v1alpha2.OperatorGroupProvidedAPIsAnnotationKey: kvgB})) + + // Ensure operatorGroupA still has KindA.version.group in its providedAPIs annotation + q = func() (metav1.ObjectMeta, error) { + g, err := crc.OperatorsV1alpha2().OperatorGroups(nsA).Get(groupA.GetName(), metav1.GetOptions{}) + return g.ObjectMeta, err + } + require.NoError(t, awaitAnnotations(t, q, map[string]string{v1alpha2.OperatorGroupProvidedAPIsAnnotationKey: kvgA})) + + // Add namespaceD to operatorGroupC's targetNamespaces + groupC, err = crc.OperatorsV1alpha2().OperatorGroups(groupC.GetNamespace()).Get(groupC.GetName(), metav1.GetOptions{}) + require.NoError(t, err) + groupC.Spec.TargetNamespaces = []string{nsC, nsD} + _, err = crc.OperatorsV1alpha2().OperatorGroups(groupC.GetNamespace()).Update(groupC) + require.NoError(t, err) + + // Wait for csvA in namespaceC to fail with status "InterOperatorGroupOwnerConflict" + fetchedCSVA, err = awaitCSV(t, crc, nsC, csvA.GetName(), csvFailedChecker) + require.NoError(t, err) + require.Equal(t, v1alpha1.CSVReasonInterOperatorGroupOwnerConflict, fetchedCSVA.Status.Reason) + + // Wait for crdA's providedAPIs to be removed from operatorGroupC's providedAPIs annotation + q = func() (metav1.ObjectMeta, error) { + g, err := crc.OperatorsV1alpha2().OperatorGroups(nsC).Get(groupC.GetName(), metav1.GetOptions{}) + return g.ObjectMeta, err + } + require.NoError(t, awaitAnnotations(t, q, map[string]string{v1alpha2.OperatorGroupProvidedAPIsAnnotationKey: ""})) + + // Ensure operatorGroupA still has KindA.version.group in its providedAPIs annotation + q = func() (metav1.ObjectMeta, error) { + g, err := crc.OperatorsV1alpha2().OperatorGroups(nsA).Get(groupA.GetName(), metav1.GetOptions{}) + return g.ObjectMeta, err + } + require.NoError(t, awaitAnnotations(t, q, map[string]string{v1alpha2.OperatorGroupProvidedAPIsAnnotationKey: kvgA})) +} + +// TODO: Test OperatorGroup resizing collisions +// TODO: Test Subscriptions with depedencies and transitive dependencies in intersecting OperatorGroups +// TODO: Test Subscription upgrade paths with + and - providedAPIs \ No newline at end of file diff --git a/test/e2e/subscription_e2e_test.go b/test/e2e/subscription_e2e_test.go index 6807d18880..1e7ef9bed8 100644 --- a/test/e2e/subscription_e2e_test.go +++ b/test/e2e/subscription_e2e_test.go @@ -388,7 +388,7 @@ func createSubscriptionForCatalog(t *testing.T, crc versioned.Interface, namespa }, Spec: &v1alpha1.SubscriptionSpec{ CatalogSource: catalog, - CatalogSourceNamespace: testNamespace, + CatalogSourceNamespace: namespace, Package: packageName, Channel: channel, StartingCSV: startingCSV, diff --git a/test/e2e/util_test.go b/test/e2e/util_test.go index c42dd741a8..082550a029 100644 --- a/test/e2e/util_test.go +++ b/test/e2e/util_test.go @@ -141,6 +141,33 @@ func awaitPods(t *testing.T, c operatorclient.ClientInterface, selector string, return fetchedPodList, err } +func awaitAnnotations(t *testing.T, query func() (metav1.ObjectMeta, error), expected map[string]string) error { + var err error + err = wait.Poll(pollInterval, pollDuration, func() (bool, error) { + t.Logf("Waiting for annotations to match %v", expected) + obj, err := query() + if err != nil && !errors.IsNotFound(err) { + return false, err + } + t.Logf("current annotations: %v", obj.GetAnnotations()) + + if len(obj.GetAnnotations()) != len(expected) { + return false, nil + } + + for key, value := range expected { + if v, ok := obj.GetAnnotations()[key]; !ok || v != value { + return false, nil + } + } + + t.Logf("Annotations match") + return true, nil + }) + + return err +} + // compareResources compares resource equality then prints a diff for easier debugging func compareResources(t *testing.T, expected, actual interface{}) { if eq := equality.Semantic.DeepEqual(expected, actual); !eq { diff --git a/vendor/github.com/emicklei/go-restful/CHANGES.md b/vendor/github.com/emicklei/go-restful/CHANGES.md index d367820c80..5597fa1cc4 100644 --- a/vendor/github.com/emicklei/go-restful/CHANGES.md +++ b/vendor/github.com/emicklei/go-restful/CHANGES.md @@ -1,8 +1,8 @@ Change history of go-restful = -v2.8.1 -- Fix Parameter 'AllowableValues' to populate swagger definition +v2.9.0 +- add per Route content encoding setting (overrides container setting) v2.8.0 - add Request.QueryParameters() diff --git a/vendor/github.com/emicklei/go-restful/container.go b/vendor/github.com/emicklei/go-restful/container.go index 56c5e9c52e..061a8d7189 100644 --- a/vendor/github.com/emicklei/go-restful/container.go +++ b/vendor/github.com/emicklei/go-restful/container.go @@ -220,9 +220,25 @@ func (c *Container) dispatch(httpWriter http.ResponseWriter, httpRequest *http.R }() } + // Find best match Route ; err is non nil if no match was found + var webService *WebService + var route *Route + var err error + func() { + c.webServicesLock.RLock() + defer c.webServicesLock.RUnlock() + webService, route, err = c.router.SelectRoute( + c.webServices, + httpRequest) + }() + // Detect if compression is needed // assume without compression, test for override - if c.contentEncodingEnabled { + contentEncodingEnabled := c.contentEncodingEnabled + if route != nil && route.contentEncodingEnabled != nil { + contentEncodingEnabled = *route.contentEncodingEnabled + } + if contentEncodingEnabled { doCompress, encoding := wantsCompressedResponse(httpRequest) if doCompress { var err error @@ -234,17 +250,7 @@ func (c *Container) dispatch(httpWriter http.ResponseWriter, httpRequest *http.R } } } - // Find best match Route ; err is non nil if no match was found - var webService *WebService - var route *Route - var err error - func() { - c.webServicesLock.RLock() - defer c.webServicesLock.RUnlock() - webService, route, err = c.router.SelectRoute( - c.webServices, - httpRequest) - }() + if err != nil { // a non-200 response has already been written // run container filters anyway ; they should not touch the response... diff --git a/vendor/github.com/emicklei/go-restful/jsr311.go b/vendor/github.com/emicklei/go-restful/jsr311.go index 4360b492ec..bc303ef709 100644 --- a/vendor/github.com/emicklei/go-restful/jsr311.go +++ b/vendor/github.com/emicklei/go-restful/jsr311.go @@ -99,11 +99,10 @@ func (r RouterJSR311) detectRoute(routes []Route, httpRequest *http.Request) (*R } return nil, NewError(http.StatusMethodNotAllowed, "405: Method Not Allowed") } - inputMediaOk := methodOk // content-type contentType := httpRequest.Header.Get(HEADER_ContentType) - inputMediaOk = []Route{} + inputMediaOk := []Route{} for _, each := range methodOk { if each.matchesContentType(contentType) { inputMediaOk = append(inputMediaOk, each) diff --git a/vendor/github.com/emicklei/go-restful/route.go b/vendor/github.com/emicklei/go-restful/route.go index f72bf98507..592638ab60 100644 --- a/vendor/github.com/emicklei/go-restful/route.go +++ b/vendor/github.com/emicklei/go-restful/route.go @@ -45,6 +45,9 @@ type Route struct { // marks a route as deprecated Deprecated bool + + //Overrides the container.contentEncodingEnabled + contentEncodingEnabled *bool } // Initialize for Route @@ -147,3 +150,8 @@ func tokenizePath(path string) []string { func (r Route) String() string { return r.Method + " " + r.Path } + +// EnableContentEncoding (default=false) allows for GZIP or DEFLATE encoding of responses. Overrides the container.contentEncodingEnabled value. +func (r Route) EnableContentEncoding(enabled bool) { + r.contentEncodingEnabled = &enabled +} diff --git a/vendor/k8s.io/apimachinery/pkg/runtime/codec.go b/vendor/k8s.io/apimachinery/pkg/runtime/codec.go index 10dc12cca9..c8a5f69b92 100644 --- a/vendor/k8s.io/apimachinery/pkg/runtime/codec.go +++ b/vendor/k8s.io/apimachinery/pkg/runtime/codec.go @@ -301,6 +301,7 @@ var _ GroupVersioner = multiGroupVersioner{} type multiGroupVersioner struct { target schema.GroupVersion acceptedGroupKinds []schema.GroupKind + coerce bool } // NewMultiGroupVersioner returns the provided group version for any kind that matches one of the provided group kinds. @@ -312,6 +313,22 @@ func NewMultiGroupVersioner(gv schema.GroupVersion, groupKinds ...schema.GroupKi return multiGroupVersioner{target: gv, acceptedGroupKinds: groupKinds} } +// NewCoercingMultiGroupVersioner returns the provided group version for any incoming kind. +// Incoming kinds that match the provided groupKinds are preferred. +// Kind may be empty in the provided group kind, in which case any kind will match. +// Examples: +// gv=mygroup/__internal, groupKinds=mygroup/Foo, anothergroup/Bar +// KindForGroupVersionKinds(yetanother/v1/Baz, anothergroup/v1/Bar) -> mygroup/__internal/Bar (matched preferred group/kind) +// +// gv=mygroup/__internal, groupKinds=mygroup, anothergroup +// KindForGroupVersionKinds(yetanother/v1/Baz, anothergroup/v1/Bar) -> mygroup/__internal/Bar (matched preferred group) +// +// gv=mygroup/__internal, groupKinds=mygroup, anothergroup +// KindForGroupVersionKinds(yetanother/v1/Baz, yetanother/v1/Bar) -> mygroup/__internal/Baz (no preferred group/kind match, uses first kind in list) +func NewCoercingMultiGroupVersioner(gv schema.GroupVersion, groupKinds ...schema.GroupKind) GroupVersioner { + return multiGroupVersioner{target: gv, acceptedGroupKinds: groupKinds, coerce: true} +} + // KindForGroupVersionKinds returns the target group version if any kind matches any of the original group kinds. It will // use the originating kind where possible. func (v multiGroupVersioner) KindForGroupVersionKinds(kinds []schema.GroupVersionKind) (schema.GroupVersionKind, bool) { @@ -326,5 +343,8 @@ func (v multiGroupVersioner) KindForGroupVersionKinds(kinds []schema.GroupVersio return v.target.WithKind(src.Kind), true } } + if v.coerce && len(kinds) > 0 { + return v.target.WithKind(kinds[0].Kind), true + } return schema.GroupVersionKind{}, false } diff --git a/vendor/k8s.io/klog/.travis.yml b/vendor/k8s.io/klog/.travis.yml index fc0d2caf33..0f508dae66 100644 --- a/vendor/k8s.io/klog/.travis.yml +++ b/vendor/k8s.io/klog/.travis.yml @@ -1,4 +1,5 @@ language: go +go_import_path: k8s.io/klog dist: xenial go: - 1.9.x diff --git a/vendor/k8s.io/klog/CONTRIBUTING.md b/vendor/k8s.io/klog/CONTRIBUTING.md index de47115137..574a56abbb 100644 --- a/vendor/k8s.io/klog/CONTRIBUTING.md +++ b/vendor/k8s.io/klog/CONTRIBUTING.md @@ -8,10 +8,6 @@ _As contributors and maintainers of this project, and in the interest of fosteri We have full documentation on how to get started contributing here: - - - [Contributor License Agreement](https://git.k8s.io/community/CLA.md) Kubernetes projects require that you sign a Contributor License Agreement (CLA) before we can accept your pull requests - [Kubernetes Contributor Guide](http://git.k8s.io/community/contributors/guide) - Main contributor documentation, or you can just jump directly to the [contributing section](http://git.k8s.io/community/contributors/guide#contributing) - [Contributor Cheat Sheet](https://git.k8s.io/community/contributors/guide/contributor-cheatsheet.md) - Common resources for existing developers @@ -20,12 +16,7 @@ If your repo has certain guidelines for contribution, put them here ahead of the - [Mentoring Initiatives](https://git.k8s.io/community/mentoring) - We have a diverse set of mentorship programs available that are always looking for volunteers! - +- [Slack](https://kubernetes.slack.com/messages/sig-architecture) +- [Mailing List](https://groups.google.com/forum/#!forum/kubernetes-sig-architecture) diff --git a/vendor/k8s.io/klog/OWNERS b/vendor/k8s.io/klog/OWNERS index 56b0eb044f..d0168e8ca6 100644 --- a/vendor/k8s.io/klog/OWNERS +++ b/vendor/k8s.io/klog/OWNERS @@ -1,4 +1,4 @@ -# See the OWNERS docs: https://git.k8s.io/community/contributors/guide/owners.md +# See the OWNERS docs at https://go.k8s.io/owners approvers: - dims diff --git a/vendor/k8s.io/klog/README.md b/vendor/k8s.io/klog/README.md index a747f538a8..6cb6d16837 100644 --- a/vendor/k8s.io/klog/README.md +++ b/vendor/k8s.io/klog/README.md @@ -5,6 +5,32 @@ klog is a permanant fork of https://github.com/golang/glog. original README from ---- +How to use klog +=============== +- Replace imports for `github.com/golang/glog` with `k8s.io/klog` +- Use `klog.InitFlags(nil)` explicitly for initializing global flags as we no longer use `init()` method to register the flags +- You can now use `log-file` instead of `log-dir` for logging to a single file (See `examples/log_file/usage_log_file.go`) +- If you want to redirect everything logged using klog somewhere else (say syslog!), you can use `klog.SetOutput()` method and supply a `io.Writer`. (See `examples/set_output/usage_set_output.go`) +- For more logging conventions (See [Logging Conventions](https://github.com/kubernetes/community/blob/master/contributors/devel/logging.md)) + +### Coexisting with glog +This package can be used side by side with glog. [This example](examples/coexist_glog/coexist_glog.go) shows how to initialize and syncronize flags from the global `flag.CommandLine` FlagSet. In addition, the example makes use of stderr as combined output by setting `alsologtostderr` (or `logtostderr`) to `true`. + +## Community, discussion, contribution, and support + +Learn how to engage with the Kubernetes community on the [community page](http://kubernetes.io/community/). + +You can reach the maintainers of this project at: + +- [Slack](https://kubernetes.slack.com/messages/sig-architecture) +- [Mailing List](https://groups.google.com/forum/#!forum/kubernetes-sig-architecture) + +### Code of conduct + +Participation in the Kubernetes community is governed by the [Kubernetes Code of Conduct](code-of-conduct.md). + +---- + glog ==== @@ -26,20 +52,20 @@ The comment from glog.go introduces the ideas: Error, Fatal, plus formatting variants such as Infof. It also provides V-style logging controlled by the -v and -vmodule=file=2 flags. - + Basic examples: - + glog.Info("Prepare to repel boarders") - + glog.Fatalf("Initialization failed: %s", err) - + See the documentation for the V function for an explanation of these examples: - + if glog.V(2) { glog.Info("Starting transaction...") } - + glog.V(2).Infoln("Processed", nItems, "elements") diff --git a/vendor/k8s.io/klog/code-of-conduct.md b/vendor/k8s.io/klog/code-of-conduct.md new file mode 100644 index 0000000000..0d15c00cf3 --- /dev/null +++ b/vendor/k8s.io/klog/code-of-conduct.md @@ -0,0 +1,3 @@ +# Kubernetes Community Code of Conduct + +Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md) diff --git a/vendor/k8s.io/klog/klog.go b/vendor/k8s.io/klog/klog.go index 13bcc81a75..733d14b731 100644 --- a/vendor/k8s.io/klog/klog.go +++ b/vendor/k8s.io/klog/klog.go @@ -43,7 +43,7 @@ // Logs are written to standard error instead of to files. // -alsologtostderr=false // Logs are written to standard error as well as to files. -// -stderrthreshold=ERROR +// -stderrthreshold=INFO // Log events at or above this severity are logged to standard // error as well as to files. // -log_dir="" @@ -396,8 +396,8 @@ type flushSyncWriter interface { } func init() { - // Default stderrThreshold is ERROR. - logging.stderrThreshold = errorLog + // Default stderrThreshold is INFO. + logging.stderrThreshold = infoLog logging.setVState(0, nil, false) go logging.flushDaemon() @@ -410,9 +410,9 @@ func InitFlags(flagset *flag.FlagSet) { } flagset.StringVar(&logging.logDir, "log_dir", "", "If non-empty, write log files in this directory") flagset.StringVar(&logging.logFile, "log_file", "", "If non-empty, use this log file") - flagset.BoolVar(&logging.toStderr, "logtostderr", false, "log to standard error instead of files") + flagset.BoolVar(&logging.toStderr, "logtostderr", true, "log to standard error instead of files") flagset.BoolVar(&logging.alsoToStderr, "alsologtostderr", false, "log to standard error as well as files") - flagset.Var(&logging.verbosity, "v", "log level for V logs") + flagset.Var(&logging.verbosity, "v", "number for the log level verbosity") flagset.BoolVar(&logging.skipHeaders, "skip_headers", false, "If true, avoid header prefixes in the log messages") flagset.Var(&logging.stderrThreshold, "stderrthreshold", "logs at or above this threshold go to stderr") flagset.Var(&logging.vmodule, "vmodule", "comma-separated list of pattern=N settings for file-filtered logging") @@ -739,7 +739,9 @@ func (l *loggingT) output(s severity, buf *buffer, file string, line int, alsoTo } data := buf.Bytes() if l.toStderr { - os.Stderr.Write(data) + if s >= l.stderrThreshold.get() { + os.Stderr.Write(data) + } } else { if alsoToStderr || l.alsoToStderr || s >= l.stderrThreshold.get() { os.Stderr.Write(data) @@ -934,7 +936,7 @@ func (l *loggingT) createFiles(sev severity) error { return nil } -const flushInterval = 30 * time.Second +const flushInterval = 5 * time.Second // flushDaemon periodically flushes the log file buffers. func (l *loggingT) flushDaemon() { diff --git a/vendor/modules.txt b/vendor/modules.txt index 3a7af7c35c..9f2290c211 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -25,13 +25,13 @@ github.com/coreos/etcd/mvcc/mvccpb github.com/coreos/etcd/pkg/tlsutil # github.com/coreos/go-semver v0.2.0 github.com/coreos/go-semver/semver -# github.com/coreos/go-systemd v0.0.0-20181031085051-9002847aa142 +# github.com/coreos/go-systemd v0.0.0-20190204112023-081494f7ee4f github.com/coreos/go-systemd/daemon # github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew/spew # github.com/elazarl/go-bindata-assetfs v1.0.0 github.com/elazarl/go-bindata-assetfs -# github.com/emicklei/go-restful v2.8.1+incompatible +# github.com/emicklei/go-restful v2.9.0+incompatible github.com/emicklei/go-restful github.com/emicklei/go-restful/log # github.com/emicklei/go-restful-swagger12 v0.0.0-20170926063155-7524189396c6 @@ -304,7 +304,7 @@ k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensio k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation k8s.io/apiextensions-apiserver/pkg/apiserver/validation k8s.io/apiextensions-apiserver/pkg/features -# k8s.io/apimachinery v0.0.0-20190118094746-1525e4dadd2d +# k8s.io/apimachinery v0.0.0-20190208202428-1a579f8a7b42 k8s.io/apimachinery/pkg/api/errors k8s.io/apimachinery/pkg/api/meta k8s.io/apimachinery/pkg/apis/meta/v1 @@ -643,7 +643,7 @@ k8s.io/gengo/namer k8s.io/gengo/types k8s.io/gengo/parser k8s.io/gengo/examples/set-gen/sets -# k8s.io/klog v0.1.0 +# k8s.io/klog v0.2.0 k8s.io/klog # k8s.io/kube-aggregator v0.0.0-20181204002017-122bac39d429 k8s.io/kube-aggregator/pkg/apis/apiregistration/v1 @@ -674,7 +674,7 @@ k8s.io/kube-openapi/pkg/generators/rules k8s.io/kube-openapi/pkg/builder k8s.io/kube-openapi/pkg/handler k8s.io/kube-openapi/pkg/util/sets -# k8s.io/kubernetes v1.11.8-beta.0.0.20190131222539-8546c0ceb197 +# k8s.io/kubernetes v1.11.8-beta.0.0.20190208223919-e6f6fa1f2dd1 k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac k8s.io/kubernetes/pkg/apis/rbac/v1 k8s.io/kubernetes/pkg/registry/rbac/validation