diff --git a/Dockerfile b/Dockerfile index f09f5bff..cc6352e9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -28,6 +28,7 @@ FROM gcr.io/distroless/static:nonroot WORKDIR / COPY --from=builder /workspace/bin/manager . COPY --from=builder /workspace/bin/status-reporter . +COPY --from=builder /workspace/bin/deployment-guard . USER 65532:65532 ENTRYPOINT ["/manager"] diff --git a/Makefile b/Makefile index b71fd16f..6dd4c8a0 100644 --- a/Makefile +++ b/Makefile @@ -99,9 +99,11 @@ uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified $(KUSTOMIZE) build config/crd | kubectl delete -f - deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config. - cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} + cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} && \ + $(KUSTOMIZE) edit set image deployment-guard=$(IMG) cd config/default && $(KUSTOMIZE) edit set image kube-rbac-proxy=$(RBAC_PROXY_IMG) - cd config/console && $(KUSTOMIZE) edit set image ocs-client-operator-console=$(OCS_CLIENT_CONSOLE_IMG) + cd config/console && $(KUSTOMIZE) edit set image ocs-client-operator-console=$(OCS_CLIENT_CONSOLE_IMG) && \ + $(KUSTOMIZE) edit set image deployment-guard=$(IMG) $(KUSTOMIZE) build config/default | sed "s|STATUS_REPORTER_IMAGE_VALUE|$(IMG)|g" | awk '{print}' | kubectl apply -f - remove: ## Remove controller from the K8s cluster specified in ~/.kube/config. @@ -118,8 +120,10 @@ remove-with-olm: ## Remove controller from the K8s cluster bundle: manifests kustomize operator-sdk yq ## Generate bundle manifests and metadata, then validate generated files. rm -rf ./bundle $(OPERATOR_SDK) generate kustomize manifests -q - cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG) + cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG) && \ + $(KUSTOMIZE) edit set image deployment-guard=$(IMG) cd config/console && $(KUSTOMIZE) edit set image ocs-client-operator-console=$(OCS_CLIENT_CONSOLE_IMG) && \ + $(KUSTOMIZE) edit set image deployment-guard=$(IMG) && \ $(KUSTOMIZE) edit set nameprefix $(OPERATOR_NAMEPREFIX) cd config/default && \ $(KUSTOMIZE) edit set image kube-rbac-proxy=$(RBAC_PROXY_IMG) && \ diff --git a/bundle.Dockerfile b/bundle.Dockerfile index 36b3cc45..ed129935 100644 --- a/bundle.Dockerfile +++ b/bundle.Dockerfile @@ -7,9 +7,9 @@ LABEL operators.operatorframework.io.bundle.metadata.v1=metadata/ LABEL operators.operatorframework.io.bundle.package.v1=ocs-client-operator LABEL operators.operatorframework.io.bundle.channels.v1=alpha LABEL operators.operatorframework.io.bundle.channel.default.v1=alpha -LABEL operators.operatorframework.io.metrics.builder=operator-sdk-v1.19.0+git +LABEL operators.operatorframework.io.metrics.builder=operator-sdk-v1.34.1 LABEL operators.operatorframework.io.metrics.mediatype.v1=metrics+v1 -LABEL operators.operatorframework.io.metrics.project_layout=go.kubebuilder.io/v3 +LABEL operators.operatorframework.io.metrics.project_layout=go.kubebuilder.io/v4 # Labels for testing. LABEL operators.operatorframework.io.test.mediatype.v1=scorecard+v1 diff --git a/bundle/manifests/ocs-client-operator.clusterserviceversion.yaml b/bundle/manifests/ocs-client-operator.clusterserviceversion.yaml index 9ffba997..c2e62b9f 100644 --- a/bundle/manifests/ocs-client-operator.clusterserviceversion.yaml +++ b/bundle/manifests/ocs-client-operator.clusterserviceversion.yaml @@ -7,15 +7,16 @@ metadata: categories: Storage console.openshift.io/plugins: '["odf-client-console"]' containerImage: quay.io/ocs-dev/ocs-client-operator:latest + createdAt: "2024-07-30T11:51:59Z" description: OpenShift Data Foundation client operator enables consumption of storage services from a remote centralized OpenShift Data Foundation provider cluster. olm.skipRange: "" operatorframework.io/suggested-namespace: openshift-storage - operators.operatorframework.io/builder: operator-sdk-v1.19.0+git + operators.operatorframework.io/builder: operator-sdk-v1.34.1 operators.operatorframework.io/internal-objects: '["storageclaims.ocs.openshift.io"]' operators.operatorframework.io/operator-type: standalone - operators.operatorframework.io/project_layout: go.kubebuilder.io/v3 + operators.operatorframework.io/project_layout: go.kubebuilder.io/v4 repository: https://github.com/red-hat-storage/ocs-client-operator support: Red Hat labels: @@ -236,6 +237,13 @@ spec: - get - patch - update + - apiGroups: + - ocs.openshift.io + resources: + - storageclusters + verbs: + - get + - list - apiGroups: - operators.coreos.com resources: @@ -751,6 +759,17 @@ spec: name: csi-images - mountPath: /etc/tls/private name: webhook-cert-secret + initContainers: + - command: + - /deployment-guard + env: + - name: OPERATOR_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + image: quay.io/ocs-dev/ocs-client-operator:latest + name: deployment-guard + resources: {} securityContext: runAsNonRoot: true serviceAccountName: ocs-client-operator-controller-manager @@ -811,14 +830,27 @@ spec: name: ocs-client-operator-console-nginx-log - mountPath: /var/lib/nginx/tmp name: ocs-client-operator-console-nginx-tmp + initContainers: + - command: + - /deployment-guard + env: + - name: OPERATOR_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + image: quay.io/ocs-dev/ocs-client-operator:latest + name: deployment-guard + resources: {} securityContext: runAsNonRoot: true + serviceAccountName: ocs-client-operator-controller-manager volumes: - name: ocs-client-operator-console-serving-cert secret: secretName: ocs-client-operator-console-serving-cert - configMap: name: ocs-client-operator-console-nginx-conf + optional: true name: ocs-client-operator-console-nginx-conf - emptyDir: {} name: ocs-client-operator-console-nginx-log diff --git a/bundle/metadata/annotations.yaml b/bundle/metadata/annotations.yaml index e64e130d..380dd987 100644 --- a/bundle/metadata/annotations.yaml +++ b/bundle/metadata/annotations.yaml @@ -6,9 +6,9 @@ annotations: operators.operatorframework.io.bundle.package.v1: ocs-client-operator operators.operatorframework.io.bundle.channels.v1: alpha operators.operatorframework.io.bundle.channel.default.v1: alpha - operators.operatorframework.io.metrics.builder: operator-sdk-v1.19.0+git + operators.operatorframework.io.metrics.builder: operator-sdk-v1.34.1 operators.operatorframework.io.metrics.mediatype.v1: metrics+v1 - operators.operatorframework.io.metrics.project_layout: go.kubebuilder.io/v3 + operators.operatorframework.io.metrics.project_layout: go.kubebuilder.io/v4 # Annotations for testing. operators.operatorframework.io.test.mediatype.v1: scorecard+v1 diff --git a/config/console/console_init.yaml b/config/console/console_init.yaml index e1ee15a0..bbb274d0 100644 --- a/config/console/console_init.yaml +++ b/config/console/console_init.yaml @@ -11,6 +11,16 @@ spec: labels: app.kubernetes.io/name: ocs-client-operator-console spec: + initContainers: + - name: deployment-guard + image: deployment-guard:latest + command: + - /deployment-guard + env: + - name: OPERATOR_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace containers: - name: ocs-client-operator-console image: ocs-client-operator-console:latest @@ -54,9 +64,11 @@ spec: - name: ocs-client-operator-console-nginx-conf configMap: name: ocs-client-operator-console-nginx-conf + optional: true - name: ocs-client-operator-console-nginx-log emptyDir: {} - name: ocs-client-operator-console-nginx-tmp emptyDir: {} securityContext: runAsNonRoot: true + serviceAccountName: ocs-client-operator-controller-manager diff --git a/config/console/kustomization.yaml b/config/console/kustomization.yaml index a70695ea..3c57a2fe 100644 --- a/config/console/kustomization.yaml +++ b/config/console/kustomization.yaml @@ -9,6 +9,9 @@ resources: apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization images: +- name: deployment-guard + newName: quay.io/ocs-dev/ocs-client-operator + newTag: latest - name: ocs-client-operator-console newName: quay.io/ocs-dev/ocs-client-console newTag: latest diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 43a5aaf6..2c910941 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -20,3 +20,6 @@ images: - name: controller newName: quay.io/ocs-dev/ocs-client-operator newTag: latest +- name: deployment-guard + newName: quay.io/ocs-dev/ocs-client-operator + newTag: latest diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index ae3c8f70..059573c7 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -29,6 +29,16 @@ spec: spec: securityContext: runAsNonRoot: true + initContainers: + - name: deployment-guard + image: deployment-guard:latest + command: + - /deployment-guard + env: + - name: OPERATOR_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace containers: - command: - /manager diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index cc5837f8..d952a963 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -194,6 +194,13 @@ rules: - get - patch - update +- apiGroups: + - ocs.openshift.io + resources: + - storageclusters + verbs: + - get + - list - apiGroups: - operators.coreos.com resources: diff --git a/hack/go-build.sh b/hack/go-build.sh index f9488ab6..cdb2e95b 100755 --- a/hack/go-build.sh +++ b/hack/go-build.sh @@ -9,3 +9,4 @@ set -x go build -a -o ${GOBIN:-bin}/manager cmd/main.go go build -a -o ${GOBIN:-bin}/status-reporter ./service/status-report/main.go +go build -a -o ${GOBIN:-bin}/deployment-guard ./service/deployment-guard/main.go diff --git a/internal/controller/operatorconfigmap_controller.go b/internal/controller/operatorconfigmap_controller.go index 528f8f8d..51dca1d7 100644 --- a/internal/controller/operatorconfigmap_controller.go +++ b/internal/controller/operatorconfigmap_controller.go @@ -177,6 +177,7 @@ func (c *OperatorConfigMapReconciler) SetupWithManager(mgr ctrl.Manager) error { //+kubebuilder:rbac:groups=console.openshift.io,resources=consoleplugins,verbs=* //+kubebuilder:rbac:groups=operators.coreos.com,resources=subscriptions,verbs=get;list;watch;update //+kubebuilder:rbac:groups=admissionregistration.k8s.io,resources=validatingwebhookconfigurations,verbs=get;list;update;create;watch;delete +//+kubebuilder:rbac:groups=ocs.openshift.io,resources=storageclusters,verbs=get;list // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.8.3/pkg/reconcile diff --git a/service/deployment-guard/main.go b/service/deployment-guard/main.go new file mode 100644 index 00000000..af6ecf88 --- /dev/null +++ b/service/deployment-guard/main.go @@ -0,0 +1,98 @@ +package main + +import ( + "context" + "os" + "time" + + "github.com/red-hat-storage/ocs-client-operator/pkg/utils" + + extv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/klog/v2" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/config" +) + +func main() { + // validations + operatorNamespace := os.Getenv(utils.OperatorNamespaceEnvVar) + if operatorNamespace == "" { + klog.Exitf("%s env var is empty", utils.OperatorNamespaceEnvVar) + } + + // creation of kube client + scheme := runtime.NewScheme() + cfg, err := config.GetConfig() + if err != nil { + klog.Exitf("Failed to get config: %v", err) + } + cl, err := client.New(cfg, client.Options{Scheme: scheme}) + if err != nil { + klog.Exitf("Failed to create controller runtime client: %v", err) + } + ctx := context.Background() + + storageClusterCRD := &metav1.PartialObjectMetadata{} + storageClusterCRD.SetGroupVersionKind( + extv1.SchemeGroupVersion.WithKind("CustomResourceDefinition"), + ) + storageClusterCRD.Name = "storageclusters.ocs.openshift.io" + + // delay exponentially from half a sec and cap at 2 minutes + delayFunc := wait.Backoff{ + Duration: 500 * time.Millisecond, + Factor: 2, + Jitter: 0.1, + Steps: 10, + Cap: 2 * time.Minute, + }.DelayFunc() + + for !allowOperatorToRun(ctx, cl, operatorNamespace) { + time.Sleep(delayFunc()) + } + +} + +func allowOperatorToRun(ctx context.Context, cl client.Client, namespace string) bool { + // verify presence of StorageCluster CRD + storageClusterCRD := &metav1.PartialObjectMetadata{} + storageClusterCRD.SetGroupVersionKind( + extv1.SchemeGroupVersion.WithKind("CustomResourceDefinition"), + ) + storageClusterCRD.Name = "storageclusters.ocs.openshift.io" + if err := cl.Get(ctx, client.ObjectKeyFromObject(storageClusterCRD), storageClusterCRD); client.IgnoreNotFound(err) != nil { + klog.Warning("Failed to find presence of StorageCluster CRD") + return false + } + + if storageClusterCRD.UID != "" { + // StorageCluster CRD exists, wait till StorageCluster CR is configured in Provider mode + storageClusters := &metav1.PartialObjectMetadataList{} + storageClusters.SetGroupVersionKind( + schema.GroupVersionKind{ + Group: "ocs.openshift.io", + Version: "v1", + Kind: "StorageCluster", + }, + ) + if err := cl.List(ctx, storageClusters, client.InNamespace(namespace), client.Limit(1)); err != nil { + klog.Warning("Failed to list StorageCluster CR") + return false + } + if len(storageClusters.Items) < 1 { + klog.Info("StorageCluster CR does not exist") + return false + } + klog.Info("Checking if StorageCluster indicates ODF is deployed in provider mode") + if storageClusters.Items[0].GetAnnotations()["ocs.openshift.io/deployment-mode"] != "provider" { + return false + } + } + + klog.Info("Condition met to allow operator to run") + return true +}