diff --git a/Makefile b/Makefile index 6a2e44db..156430fe 100755 --- a/Makefile +++ b/Makefile @@ -72,6 +72,7 @@ build: generate GO111MODULE=on go install ./cmd/manager GO111MODULE=on go install ./cmd/manager/stack GO111MODULE=on go install ./cmd/admission-webhook + GO111MODULE=on go install ./cmd/devfile-registry-controller build-image: generate # These commands were taken from operator-sdk 0.8.1. The sdk did not let us @@ -81,6 +82,7 @@ build-image: generate GO111MODULE=on CGO_ENABLED=0 GOOS=linux GOARCH=$(ARCH) go build -o build/_output/bin/kabanero-operator -gcflags "all=-trimpath=$(GOPATH)" -asmflags "all=-trimpath=$(GOPATH)" -ldflags "-X main.GitTag=$(TRAVIS_TAG) -X main.GitCommit=$(TRAVIS_COMMIT) -X main.GitRepoSlug=$(TRAVIS_REPO_SLUG) -X main.BuildDate=`date -u +%Y%m%d.%H%M%S`" github.com/kabanero-io/kabanero-operator/cmd/manager GO111MODULE=on CGO_ENABLED=0 GOOS=linux GOARCH=$(ARCH) go build -o build/_output/bin/kabanero-operator-stack-controller -gcflags "all=-trimpath=$(GOPATH)" -asmflags "all=-trimpath=$(GOPATH)" -ldflags "-X main.GitTag=$(TRAVIS_TAG) -X main.GitCommit=$(TRAVIS_COMMIT) -X main.GitRepoSlug=$(TRAVIS_REPO_SLUG) -X main.BuildDate=`date -u +%Y%m%d.%H%M%S`" github.com/kabanero-io/kabanero-operator/cmd/manager/stack GO111MODULE=on CGO_ENABLED=0 GOOS=linux GOARCH=$(ARCH) go build -o build/_output/bin/admission-webhook -gcflags "all=-trimpath=$(GOPATH)" -asmflags "all=-trimpath=$(GOPATH)" -ldflags "-X main.GitTag=$(TRAVIS_TAG) -X main.GitCommit=$(TRAVIS_COMMIT) -X main.GitRepoSlug=$(TRAVIS_REPO_SLUG) -X main.BuildDate=`date -u +%Y%m%d.%H%M%S`" github.com/kabanero-io/kabanero-operator/cmd/admission-webhook + GO111MODULE=on CGO_ENABLED=0 GOOS=linux GOARCH=$(ARCH) go build -o build/_output/bin/devfile-registry-controller -gcflags "all=-trimpath=$(GOPATH)" -asmflags "all=-trimpath=$(GOPATH)" -ldflags "-X main.GitTag=$(TRAVIS_TAG) -X main.GitCommit=$(TRAVIS_COMMIT) -X main.GitRepoSlug=$(TRAVIS_REPO_SLUG) -X main.BuildDate=`date -u +%Y%m%d.%H%M%S`" github.com/kabanero-io/kabanero-operator/cmd/devfile-registry-controller docker build -f build/Dockerfile -t $(IMAGE) . @@ -195,7 +197,7 @@ ifndef GITHUB_TOKEN endif mkdir -p build/bin curl -L https://github.com/mitchellh/golicense/releases/download/v0.2.0/golicense_0.2.0_$(detected_OS)_x86_64.tar.gz | tar -C build/bin -xzf - golicense - build/bin/golicense -plain ./license-rules.json build/_output/bin/admission-webhook build/_output/bin/kabanero-operator build/_output/bin/kabanero-operator-stack-controller | sort > 3RD_PARTY || true + build/bin/golicense -plain ./license-rules.json build/_output/bin/admission-webhook build/_output/bin/kabanero-operator build/_output/bin/kabanero-operator-stack-controller build/_output/bin/devfile-registry-controller | sort > 3RD_PARTY || true rm build/bin/golicense # Integration Tests diff --git a/build/Dockerfile b/build/Dockerfile index ff0bd4eb..66d9f329 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -17,6 +17,9 @@ ENV OPERATOR=/usr/local/bin/kabanero-operator \ COPY build/_output/bin/kabanero-operator ${OPERATOR} COPY build/_output/bin/kabanero-operator-stack-controller /usr/local/bin/kabanero-operator-stack-controller COPY build/_output/bin/admission-webhook /usr/local/bin/admission-webhook +COPY build/_output/bin/devfile-registry-controller /usr/local/bin/devfile-registry-controller + +RUN mkdir /devfiles && chmod +777 /devfiles COPY build/bin /usr/local/bin RUN /usr/local/bin/user_setup diff --git a/cmd/devfile-registry-controller/main.go b/cmd/devfile-registry-controller/main.go new file mode 100644 index 00000000..86b4086d --- /dev/null +++ b/cmd/devfile-registry-controller/main.go @@ -0,0 +1,232 @@ +package main + +import ( + "context" + "errors" + "flag" + "fmt" + "os" + // "runtime" + "strings" + "net/http" + + // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) + _ "k8s.io/client-go/plugin/pkg/client/auth" + "k8s.io/client-go/rest" + + "github.com/kabanero-io/kabanero-operator/pkg/apis" + "github.com/kabanero-io/kabanero-operator/pkg/controller/devfileregistry" + + "github.com/operator-framework/operator-sdk/pkg/k8sutil" + kubemetrics "github.com/operator-framework/operator-sdk/pkg/kube-metrics" + "github.com/operator-framework/operator-sdk/pkg/leader" + "github.com/operator-framework/operator-sdk/pkg/log/zap" + "github.com/operator-framework/operator-sdk/pkg/metrics" + // sdkVersion "github.com/operator-framework/operator-sdk/version" + "github.com/spf13/pflag" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/intstr" + "sigs.k8s.io/controller-runtime/pkg/cache" + "sigs.k8s.io/controller-runtime/pkg/client/config" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/manager/signals" +) + +// Change below variables to serve metrics on different host or port. +var ( + metricsHost = "0.0.0.0" + metricsPort int32 = 8383 + operatorMetricsPort int32 = 8686 +) +var log = logf.Log.WithName("cmd") + +func main() { + // Add the zap logger flag set to the CLI. The flag set must + // be added before calling pflag.Parse(). + pflag.CommandLine.AddFlagSet(zap.FlagSet()) + + // Add flags registered by imported packages (e.g. glog and + // controller-runtime) + pflag.CommandLine.AddGoFlagSet(flag.CommandLine) + + pflag.Parse() + + // Use a zap logr.Logger implementation. If none of the zap + // flags are configured (or if the zap flag set is not being + // used), this defaults to a production zap logger. + // + // The logger instantiated here can be changed to any logger + // implementing the logr.Logger interface. This logger will + // be propagated through the whole operator, generating + // uniform and structured logs. + logf.SetLogger(zap.Logger()) + + namespace, err := k8sutil.GetWatchNamespace() + if err != nil { + log.Error(err, "Failed to get watch namespace") + os.Exit(1) + } + + // Get a config to talk to the apiserver + cfg, err := config.GetConfig() + if err != nil { + log.Error(err, "") + os.Exit(1) + } + + ctx := context.TODO() + // Become the leader before proceeding + err = leader.Become(ctx, "devfile-registry-lock") + if err != nil { + log.Error(err, "") + os.Exit(1) + } + + // Set default manager options + options := manager.Options{ + Namespace: namespace, + MetricsBindAddress: fmt.Sprintf("%s:%d", metricsHost, metricsPort), + } + + // Add support for MultiNamespace set in WATCH_NAMESPACE (e.g ns1,ns2) + // Note that this is not intended to be used for excluding namespaces, this is better done via a Predicate + // Also note that you may face performance issues when using this with a high number of namespaces. + // More Info: https://godoc.org/github.com/kubernetes-sigs/controller-runtime/pkg/cache#MultiNamespacedCacheBuilder + if strings.Contains(namespace, ",") { + options.Namespace = "" + options.NewCache = cache.MultiNamespacedCacheBuilder(strings.Split(namespace, ",")) + } + + // Create a new manager to provide shared dependencies and start components + mgr, err := manager.New(cfg, options) + if err != nil { + log.Error(err, "") + os.Exit(1) + } + + log.Info("Registering Components.") + + // Setup Scheme for all resources + if err := apis.AddToScheme(mgr.GetScheme()); err != nil { + log.Error(err, "") + os.Exit(1) + } + + // Setup all Controllers + if err := devfileregistry.AddToManager(mgr); err != nil { + log.Error(err, "") + os.Exit(1) + } + + // Add the Metrics Service + addMetrics(ctx, cfg) + + //Start HTTTPS & Cmd + errs := Run(mgr) + + // Run until channel receives error + select { + case err := <-errs: + log.Error(err, "Manager exited non-zero") + os.Exit(1) + } + +} + +func Run(mgr manager.Manager) chan error { + + errs := make(chan error) + + // Start serving devfiles index + go func() { + fs := http.FileServer(http.Dir("/devfiles")) + http.Handle("/", fs) + log.Info("Starting Devfile registry on port :8443...") + if err := http.ListenAndServeTLS(":8443","/tmp/serving-certs/tls.crt","/tmp/serving-certs/tls.key",nil); err != nil { + errs <- err + } + }() + + // Start the Cmd + go func() { + log.Info("Starting the Cmd.") + if err := mgr.Start(signals.SetupSignalHandler()); err != nil { + errs <- err + } + }() + + return errs +} + + + +// addMetrics will create the Services and Service Monitors to allow the operator export the metrics by using +// the Prometheus operator +func addMetrics(ctx context.Context, cfg *rest.Config) { + // Get the namespace the operator is currently deployed in. + operatorNs, err := k8sutil.GetOperatorNamespace() + if err != nil { + if errors.Is(err, k8sutil.ErrRunLocal) { + log.Info("Skipping CR metrics server creation; not running in a cluster.") + return + } + } + + if err := serveCRMetrics(cfg, operatorNs); err != nil { + log.Info("Could not generate and serve custom resource metrics", "error", err.Error()) + } + + // Add to the below struct any other metrics ports you want to expose. + servicePorts := []v1.ServicePort{ + {Port: metricsPort, Name: metrics.OperatorPortName, Protocol: v1.ProtocolTCP, TargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: metricsPort}}, + {Port: operatorMetricsPort, Name: metrics.CRPortName, Protocol: v1.ProtocolTCP, TargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: operatorMetricsPort}}, + } + + // Create Service object to expose the metrics port(s). + service, err := metrics.CreateMetricsService(ctx, cfg, servicePorts) + if err != nil { + log.Info("Could not create metrics Service", "error", err.Error()) + } + + // CreateServiceMonitors will automatically create the prometheus-operator ServiceMonitor resources + // necessary to configure Prometheus to scrape metrics from this operator. + services := []*v1.Service{service} + + // The ServiceMonitor is created in the same namespace where the operator is deployed + _, err = metrics.CreateServiceMonitors(cfg, operatorNs, services) + if err != nil { + log.Info("Could not create ServiceMonitor object", "error", err.Error()) + // If this operator is deployed to a cluster without the prometheus-operator running, it will return + // ErrServiceMonitorNotPresent, which can be used to safely skip ServiceMonitor creation. + if err == metrics.ErrServiceMonitorNotPresent { + log.Info("Install prometheus-operator in your cluster to create ServiceMonitor objects", "error", err.Error()) + } + } +} + +// serveCRMetrics gets the Operator/CustomResource GVKs and generates metrics based on those types. +// It serves those metrics on "http://metricsHost:operatorMetricsPort". +func serveCRMetrics(cfg *rest.Config, operatorNs string) error { + // The function below returns a list of filtered operator/CR specific GVKs. For more control, override the GVK list below + // with your own custom logic. Note that if you are adding third party API schemas, probably you will need to + // customize this implementation to avoid permissions issues. + filteredGVK, err := k8sutil.GetGVKsFromAddToScheme(apis.AddToScheme) + if err != nil { + return err + } + + // The metrics will be generated from the namespaces which are returned here. + // NOTE that passing nil or an empty list of namespaces in GenerateAndServeCRMetrics will result in an error. + ns, err := kubemetrics.GetNamespacesForMetrics(operatorNs) + if err != nil { + return err + } + + // Generate and serve custom resource specific metrics. + err = kubemetrics.GenerateAndServeCRMetrics(cfg, ns, filteredGVK, metricsHost, operatorMetricsPort) + if err != nil { + return err + } + return nil +} diff --git a/config/orchestrations/devfile-registry-controller/0.1/devfile-registry-controller.yaml b/config/orchestrations/devfile-registry-controller/0.1/devfile-registry-controller.yaml new file mode 100644 index 00000000..31bb0ad6 --- /dev/null +++ b/config/orchestrations/devfile-registry-controller/0.1/devfile-registry-controller.yaml @@ -0,0 +1,173 @@ +apiVersion: v1 +kind: Service +metadata: + name: kabanero-operator-devfile-registry + annotations: + service.beta.openshift.io/serving-cert-secret-name: kabanero-operator-devfile-registry-cert + labels: + app.kubernetes.io/name: kabanero-operator-devfile-registry + app.kubernetes.io/instance: {{ .instance }} + app.kubernetes.io/version: {{ .version }} + app.kubernetes.io/component: devfile-registry + app.kubernetes.io/part-of: kabanero + app.kubernetes.io/managed-by: kabanero-operator +spec: + selector: + name: kabanero-operator-devfile-registry + ports: + - protocol: TCP + port: 443 + targetPort: 8443 +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + creationTimestamp: null + name: kabanero-operator-devfile-registry +rules: +- apiGroups: + - "" + resources: + - pods + - services + - services/finalizers + - endpoints + - persistentvolumeclaims + - events + - configmaps + - secrets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - apps + resources: + - deployments + - daemonsets + - replicasets + - statefulsets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - monitoring.coreos.com + resources: + - servicemonitors + verbs: + - "get" + - "create" +- apiGroups: + - "" + resources: + - pods + verbs: + - get +- apiGroups: + - apps + resources: + - replicasets + - deployments + verbs: + - get +- apiGroups: + - kabanero.io + resources: + - "*" + verbs: + - "get" + - "list" + - "watch" +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: kabanero-operator-devfile-registry +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: kabanero-operator-devfile-registry +subjects: +- kind: ServiceAccount + name: kabanero-operator-devfile-registry +roleRef: + kind: Role + name: kabanero-operator-devfile-registry + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: route.openshift.io/v1 +kind: Route +metadata: + name: kabanero-operator-devfile-registry +spec: + to: + kind: Service + name: kabanero-operator-devfile-registry + tls: + termination: reencrypt + insecureEdgeTerminationPolicy: Redirect +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: kabanero-operator-devfile-registry + labels: + name: kabanero-operator-devfile-registry + app.kubernetes.io/name: kabanero-operator-devfile-registry + app.kubernetes.io/instance: {{ .instance }} + app.kubernetes.io/version: {{ .version }} + app.kubernetes.io/component: devfile-registry + app.kubernetes.io/part-of: kabanero + app.kubernetes.io/managed-by: kabanero-operator +spec: + replicas: 1 + selector: + matchLabels: + name: kabanero-operator-devfile-registry + template: + metadata: + labels: + name: kabanero-operator-devfile-registry + app.kubernetes.io/name: kabanero-operator-devfile-registry + app.kubernetes.io/instance: {{ .instance }} + app.kubernetes.io/version: {{ .version }} + app.kubernetes.io/component: devfile-registry + app.kubernetes.io/part-of: kabanero + app.kubernetes.io/managed-by: kabanero-operator + spec: + serviceAccount: kabanero-operator-devfile-registry + containers: + - name: kabanero-operator-devfile-registry + image: {{ .image }} + imagePullPolicy: Always + command: + - /usr/local/bin/devfile-registry-controller + env: + - name: WATCH_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: OPERATOR_NAME + value: "kabanero-operator-devfile-registry" + volumeMounts: + - mountPath: /tmp/serving-certs + name: kabanero-operator-devfile-registry-cert + readOnly: true + volumes: + - name: kabanero-operator-devfile-registry-cert + secret: + secretName: kabanero-operator-devfile-registry-cert diff --git a/config/samples/full.yaml b/config/samples/full.yaml index 1dddc03d..6fd9298f 100644 --- a/config/samples/full.yaml +++ b/config/samples/full.yaml @@ -66,6 +66,17 @@ spec: # Overrides the image uri image: kabanero/kabanero-operator:TRAVIS_TAG + + devfileRegistry: + # Overrides the setting for version on this component + version: "0.10.0" + + # Overrides the image as a separate repository or tag + repository: kabanero/kabanero-operator + tag: "TRAVIS_TAG" + + # Overrides the image uri + image: kabanero/kabanero-operator:TRAVIS_TAG codeReadyWorkspaces: # CodeReadyWorkspaces CR instance deployment is disabled by default. To enable it, set the enable value to true. diff --git a/config/versions.yaml b/config/versions.yaml index c32b9b92..cf8b8929 100644 --- a/config/versions.yaml +++ b/config/versions.yaml @@ -22,6 +22,7 @@ kabanero: admission-webhook: "0.10.0" sso: "7.3.2" codeready-workspaces: "0.10.0" + devfile-registry-controller: "0.10.0" - version: "0.9.1" related-versions: @@ -302,3 +303,10 @@ related-software: sso: - version: "7.3.2" orchestrations: "orchestrations/sso/0.1" + + devfile-registry-controller: + - version: "0.10.0" + orchestrations: "orchestrations/devfile-registry-controller/0.1" + identifiers: + repository: "FROM_POD" + tag: "FROM_POD" diff --git a/deploy/crds/kabanero.io_kabaneros_crd.yaml b/deploy/crds/kabanero.io_kabaneros_crd.yaml index b01128b3..bf34d8e0 100644 --- a/deploy/crds/kabanero.io_kabaneros_crd.yaml +++ b/deploy/crds/kabanero.io_kabaneros_crd.yaml @@ -135,7 +135,9 @@ spec: type: string type: object type: array - x-kubernetes-list-type: set + x-kubernetes-list-map-keys: + - url + x-kubernetes-list-type: map type: object events: properties: @@ -466,6 +468,17 @@ spec: version: type: string type: object + devfileRegistry: + properties: + image: + type: string + repository: + type: string + tag: + type: string + version: + type: string + type: object events: properties: enable: @@ -534,7 +547,10 @@ spec: type: string type: object type: array - x-kubernetes-list-type: set + x-kubernetes-list-map-keys: + - id + - sha256 + x-kubernetes-list-type: map type: object governancePolicy: description: GovernancePolicyConfig defines customization entries @@ -621,7 +637,10 @@ spec: type: string type: object type: array - x-kubernetes-list-type: set + x-kubernetes-list-map-keys: + - id + - sha256 + x-kubernetes-list-type: map repositories: items: description: RepositoryConfig defines customization entries @@ -692,10 +711,15 @@ spec: type: string type: object type: array - x-kubernetes-list-type: set + x-kubernetes-list-map-keys: + - id + - sha256 + x-kubernetes-list-type: map type: object type: array - x-kubernetes-list-type: set + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map skipRegistryCertVerification: type: boolean type: object @@ -741,7 +765,10 @@ spec: type: string type: object type: array - x-kubernetes-list-type: set + x-kubernetes-list-map-keys: + - id + - sha256 + x-kubernetes-list-type: map version: type: string type: object @@ -872,7 +899,13 @@ spec: type: string type: object type: array - x-kubernetes-list-type: set + x-kubernetes-list-map-keys: + - assetName + - namespace + - group + - version + - kind + x-kubernetes-list-type: map digest: type: string gitRelease: @@ -897,6 +930,10 @@ spec: type: string type: object type: array + x-kubernetes-list-map-keys: + - name + - digest + x-kubernetes-list-type: map ready: type: string type: object diff --git a/deploy/crds/kabanero.io_stacks_crd.yaml b/deploy/crds/kabanero.io_stacks_crd.yaml index e7241b60..8b2dd02b 100644 --- a/deploy/crds/kabanero.io_stacks_crd.yaml +++ b/deploy/crds/kabanero.io_stacks_crd.yaml @@ -51,6 +51,8 @@ spec: properties: desiredState: type: string + devfile: + type: string images: items: description: Image defines a container image used by a stack @@ -61,7 +63,12 @@ spec: type: string type: object type: array - x-kubernetes-list-type: set + x-kubernetes-list-map-keys: + - id + - image + x-kubernetes-list-type: map + metafile: + type: string pipelines: items: description: PipelineSpec defines a set of pipelines and associated @@ -99,7 +106,10 @@ spec: type: string type: object type: array - x-kubernetes-list-type: set + x-kubernetes-list-map-keys: + - id + - sha256 + x-kubernetes-list-type: map skipCertVerification: type: boolean skipRegistryCertVerification: @@ -108,7 +118,9 @@ spec: type: string type: object type: array - x-kubernetes-list-type: set + x-kubernetes-list-map-keys: + - version + x-kubernetes-list-type: map type: object status: description: StackStatus defines the observed state of a stack @@ -142,7 +154,10 @@ spec: type: string type: object type: array - x-kubernetes-list-type: set + x-kubernetes-list-map-keys: + - id + - image + x-kubernetes-list-type: map location: type: string pipelines: @@ -173,7 +188,13 @@ spec: type: string type: object type: array - x-kubernetes-list-type: set + x-kubernetes-list-map-keys: + - assetName + - namespace + - group + - version + - kind + x-kubernetes-list-type: map digest: type: string gitRelease: @@ -198,7 +219,10 @@ spec: type: string type: object type: array - x-kubernetes-list-type: set + x-kubernetes-list-map-keys: + - name + - digest + x-kubernetes-list-type: map status: type: string statusMessage: @@ -207,7 +231,9 @@ spec: type: string type: object type: array - x-kubernetes-list-type: set + x-kubernetes-list-map-keys: + - version + x-kubernetes-list-type: map type: object type: object version: v1alpha2 diff --git a/go.mod b/go.mod index 8d44f811..2d7f6c94 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/docker/cli v0.0.0-20200210162036-a4bedce16568 github.com/docker/distribution v2.7.1+incompatible github.com/docker/docker v1.13.1 + github.com/elsony/devfile2-registry/tools v0.0.0-20200603181527-db339ef8dd30 github.com/go-logr/logr v0.1.0 github.com/go-openapi/spec v0.19.6 github.com/google/go-cmp v0.4.0 diff --git a/go.sum b/go.sum index 8cae441e..1b703959 100644 --- a/go.sum +++ b/go.sum @@ -399,6 +399,8 @@ github.com/elastic/go-windows v1.0.1/go.mod h1:FoVvqWSun28vaDQPbj2Elfc0JahhPB7WQ github.com/elastic/gosigar v0.9.0/go.mod h1:cdorVVzy1fhmEqmtgqkoE3bYtCfSCkVyjTyCIo22xvs= github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/elsony/devfile2-registry/tools v0.0.0-20200603181527-db339ef8dd30 h1:ogOtSuypgF1OHCAYE9gF5amZvDlmfwOYnAydWjEhXUU= +github.com/elsony/devfile2-registry/tools v0.0.0-20200603181527-db339ef8dd30/go.mod h1:MLIyyFUh3cQoyfvXXosqS2rr4twO106QiYGjyJrqiYY= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.6.0+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= diff --git a/pkg/apis/kabanero/v1alpha1/kabanero_types.go b/pkg/apis/kabanero/v1alpha1/kabanero_types.go index 583f75ab..f73ca5fd 100644 --- a/pkg/apis/kabanero/v1alpha1/kabanero_types.go +++ b/pkg/apis/kabanero/v1alpha1/kabanero_types.go @@ -43,7 +43,8 @@ type KabaneroSpec struct { // InstanceCollectionConfig defines the customization entries for a set of collections. type InstanceCollectionConfig struct { - // +listType=set + // +listType=map + // +listMapKey=url Repositories []RepositoryConfig `json:"repositories,omitempty"` } diff --git a/pkg/apis/kabanero/v1alpha2/kabanero_types.go b/pkg/apis/kabanero/v1alpha2/kabanero_types.go index 323bf01e..226254b3 100644 --- a/pkg/apis/kabanero/v1alpha2/kabanero_types.go +++ b/pkg/apis/kabanero/v1alpha2/kabanero_types.go @@ -28,7 +28,9 @@ type KabaneroSpec struct { Stacks InstanceStackConfig `json:"stacks,omitempty"` - // +listType=set + // +listType=map + // +listMapKey=id + // +listMapKey=sha256 Triggers []TriggerSpec `json:"triggers,omitempty"` CliServices KabaneroCliServicesCustomizationSpec `json:"cliServices,omitempty"` @@ -45,13 +47,17 @@ type KabaneroSpec struct { AdmissionControllerWebhook AdmissionControllerWebhookCustomizationSpec `json:"admissionControllerWebhook,omitempty"` + DevfileRegistry DevfileRegistrySpec `json:"devfileRegistry,omitempty"` + Sso SsoCustomizationSpec `json:"sso,omitempty"` Gitops GitopsSpec `json:"gitops,omitempty"` } type GitopsSpec struct { - // +listType=set + // +listType=map + // +listMapKey=id + // +listMapKey=sha256 Pipelines []PipelineSpec `json:"pipelines,omitempty"` } @@ -71,10 +77,13 @@ func (gs GitopsSpec) GetPipelines() []PipelineSpec { type InstanceStackConfig struct { SkipRegistryCertVerification bool `json:"skipRegistryCertVerification,omitempty"` - // +listType=set + // +listType=map + // +listMapKey=name Repositories []RepositoryConfig `json:"repositories,omitempty"` - // +listType=set + // +listType=map + // +listMapKey=id + // +listMapKey=sha256 Pipelines []PipelineSpec `json:"pipelines,omitempty"` } @@ -119,7 +128,9 @@ type GovernancePolicyConfig struct { // RepositoryConfig defines customization entries for a stack. type RepositoryConfig struct { Name string `json:"name,omitempty"` - // +listType=set + // +listType=map + // +listMapKey=id + // +listMapKey=sha256 Pipelines []PipelineSpec `json:"pipelines,omitempty"` Https HttpsProtocolFile `json:"https,omitempty"` GitRelease GitReleaseSpec `json:"gitRelease,omitempty"` @@ -232,6 +243,13 @@ type AdmissionControllerWebhookCustomizationSpec struct { Tag string `json:"tag,omitempty"` } +type DevfileRegistrySpec struct { + Version string `json:"version,omitempty"` + Image string `json:"image,omitempty"` + Repository string `json:"repository,omitempty"` + Tag string `json:"tag,omitempty"` +} + type SsoCustomizationSpec struct { Enable bool `json:"enable,omitempty"` Provider string `json:"provider,omitempty"` @@ -302,12 +320,20 @@ type PipelineStatus struct { Url string `json:"url,omitempty"` GitRelease GitReleaseInfo `json:"gitRelease,omitempty"` Digest string `json:"digest,omitempty"` - // +listType=set + // +listType=map + // +listMapKey=assetName + // +listMapKey=namespace + // +listMapKey=group + // +listMapKey=version + // +listMapKey=kind ActiveAssets []RepositoryAssetStatus `json:"activeAssets,omitempty"` } // The status of the gitops pipelines type GitopsStatus struct { + // +listType=map + // +listMapKey=name + // +listMapKey=digest Pipelines []PipelineStatus `json:"pipelines,omitempty"` Ready string `json:"ready,omitempty"` Message string `json:"message,omitempty"` diff --git a/pkg/apis/kabanero/v1alpha2/stack_types.go b/pkg/apis/kabanero/v1alpha2/stack_types.go index b90faf18..cc28d28b 100644 --- a/pkg/apis/kabanero/v1alpha2/stack_types.go +++ b/pkg/apis/kabanero/v1alpha2/stack_types.go @@ -39,7 +39,8 @@ const ( // +k8s:openapi-gen=true type StackSpec struct { Name string `json:"name,omitempty"` - // +listType=set + // +listType=map + // +listMapKey=version Versions []StackVersion `json:"versions,omitempty"` } @@ -55,13 +56,19 @@ func (s StackSpec) GetVersions() []ComponentSpecVersion { type StackVersion struct { SkipRegistryCertVerification bool `json:"skipRegistryCertVerification,omitempty"` - // +listType=set + // +listType=map + // +listMapKey=id + // +listMapKey=sha256 Pipelines []PipelineSpec `json:"pipelines,omitempty"` Version string `json:"version,omitempty"` DesiredState string `json:"desiredState,omitempty"` SkipCertVerification bool `json:"skipCertVerification,omitempty"` - // +listType=set - Images []Image `json:"images,omitempty"` + // +listType=map + // +listMapKey=id + // +listMapKey=image + Images []Image `json:"images,omitempty"` + Devfile string `json:"devfile,omitempty"` + Metafile string `json:"metafile,omitempty"` } func (sv StackVersion) GetVersion() string { @@ -110,7 +117,8 @@ type RepositoryAssetStatus struct { // +k8s:openapi-gen=true type StackStatus struct { StatusMessage string `json:"statusMessage,omitempty"` - // +listType=set + // +listType=map + // +listMapKey=version Versions []StackVersionStatus `json:"versions,omitempty"` Summary string `json:"summary,omitempty"` } @@ -127,11 +135,15 @@ func (s StackStatus) GetVersions() []ComponentStatusVersion { type StackVersionStatus struct { Version string `json:"version,omitempty"` Location string `json:"location,omitempty"` - // +listType=set + // +listType=map + // +listMapKey=name + // +listMapKey=digest Pipelines []PipelineStatus `json:"pipelines,omitempty"` Status string `json:"status,omitempty"` StatusMessage string `json:"statusMessage,omitempty"` - // +listType=set + // +listType=map + // +listMapKey=id + // +listMapKey=image Images []ImageStatus `json:"images,omitempty"` } diff --git a/pkg/apis/kabanero/v1alpha2/zz_generated.deepcopy.go b/pkg/apis/kabanero/v1alpha2/zz_generated.deepcopy.go index f9776db6..6428cfa9 100644 --- a/pkg/apis/kabanero/v1alpha2/zz_generated.deepcopy.go +++ b/pkg/apis/kabanero/v1alpha2/zz_generated.deepcopy.go @@ -246,6 +246,22 @@ func (in *CollectionControllerStatus) DeepCopy() *CollectionControllerStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DevfileRegistrySpec) DeepCopyInto(out *DevfileRegistrySpec) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DevfileRegistrySpec. +func (in *DevfileRegistrySpec) DeepCopy() *DevfileRegistrySpec { + if in == nil { + return nil + } + out := new(DevfileRegistrySpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *EventsCustomizationSpec) DeepCopyInto(out *EventsCustomizationSpec) { *out = *in @@ -647,6 +663,7 @@ func (in *KabaneroSpec) DeepCopyInto(out *KabaneroSpec) { out.CollectionController = in.CollectionController out.StackController = in.StackController out.AdmissionControllerWebhook = in.AdmissionControllerWebhook + out.DevfileRegistry = in.DevfileRegistry out.Sso = in.Sso in.Gitops.DeepCopyInto(&out.Gitops) return diff --git a/pkg/controller/devfileregistry/controller.go b/pkg/controller/devfileregistry/controller.go new file mode 100644 index 00000000..82bda823 --- /dev/null +++ b/pkg/controller/devfileregistry/controller.go @@ -0,0 +1,18 @@ +package devfileregistry + +import ( + "sigs.k8s.io/controller-runtime/pkg/manager" +) + +// AddToManagerFuncs is a list of functions to add all Controllers to the Manager +var AddToManagerFuncs []func(manager.Manager) error + +// AddToManager adds all Controllers to the Manager +func AddToManager(m manager.Manager) error { + for _, f := range AddToManagerFuncs { + if err := f(m); err != nil { + return err + } + } + return nil +} diff --git a/pkg/controller/devfileregistry/devfile_registry_controller.go b/pkg/controller/devfileregistry/devfile_registry_controller.go new file mode 100644 index 00000000..9253f696 --- /dev/null +++ b/pkg/controller/devfileregistry/devfile_registry_controller.go @@ -0,0 +1,169 @@ +package devfileregistry + +import ( + "context" + "fmt" + "os" + + kabanerov1alpha2 "github.com/kabanero-io/kabanero-operator/pkg/apis/kabanero/v1alpha2" + // corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + // metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + // "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + // "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/handler" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/controller-runtime/pkg/source" + + // Prefer to import index func when it's moved to a pkg + // d2index "github.com/odo-devfiles/registry/tools/cmd/index" + "encoding/json" +) + +var log = logf.Log.WithName("controller_stack") + +// Add creates a new Stack Controller and adds it to the Manager. The Manager will set fields on the Controller +// and Start it when the Manager is Started. +func Add(mgr manager.Manager) error { + return add(mgr, newReconciler(mgr)) +} + +// newReconciler returns a new reconcile.Reconciler +func newReconciler(mgr manager.Manager) reconcile.Reconciler { + return &ReconcileStack{client: mgr.GetClient(), scheme: mgr.GetScheme()} +} + +// add adds a new Controller to mgr with r as the reconcile.Reconciler +func add(mgr manager.Manager, r reconcile.Reconciler) error { + // Create a new controller + c, err := controller.New("stack-controller", mgr, controller.Options{Reconciler: r}) + if err != nil { + return err + } + + // Watch for changes to primary resource Stack + err = c.Watch(&source.Kind{Type: &kabanerov1alpha2.Stack{}}, &handler.EnqueueRequestForObject{}) + if err != nil { + return err + } + + return nil +} + +// blank assignment to verify that ReconcileStack implements reconcile.Reconciler +var _ reconcile.Reconciler = &ReconcileStack{} + +// ReconcileStack reconciles a Stack object +type ReconcileStack struct { + // This client, initialized using mgr.Client() above, is a split client + // that reads objects from the cache and writes to the apiserver + client client.Client + scheme *runtime.Scheme +} + +// Reconcile reads that state of the cluster for a Stack object and makes changes based on the state read +// and what is in the Stack.Spec +// TODO(user): Modify this Reconcile function to implement your Controller logic. This example creates +// a Pod as an example +// Note: +// The Controller will requeue the Request to be processed again if the returned error is non-nil or +// Result.Requeue is true, otherwise upon completion it will remove the work from the queue. +func (r *ReconcileStack) Reconcile(request reconcile.Request) (reconcile.Result, error) { + reqLogger := log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name) + reqLogger.Info("Reconciling Stack") + + // Fetch the Stack instance + instance := &kabanerov1alpha2.Stack{} + err := r.client.Get(context.TODO(), request.NamespacedName, instance) + if err != nil { + if errors.IsNotFound(err) { + // Request object not found, could have been deleted after reconcile request. + // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. + // Return and don't requeue + return reconcile.Result{}, nil + } + // Error reading the object - requeue the request. + return reconcile.Result{}, err + } + + basePath := "/devfiles" + stackPath := fmt.Sprintf("%v/%v", basePath, instance.ObjectMeta.Name) + os.Mkdir(basePath, os.ModePerm) + + // Clean all files for this stack: a version may be removed or Stack deletion + os.RemoveAll(stackPath) + + beingDeleted := !instance.DeletionTimestamp.IsZero() + + if !beingDeleted { + // Copy the yaml from the Stack to local devfile registry if it exists + for _, version := range instance.Spec.Versions { + if (len(version.Devfile) !=0) && (len(version.Metafile) !=0) { + versionPath := fmt.Sprintf("%v/%v", stackPath, version.Version) + devfile := fmt.Sprintf("%v/devfile.yaml", versionPath) + metafile := fmt.Sprintf("%v/meta.yaml", versionPath) + + os.MkdirAll(versionPath, os.ModePerm) + + f, err := os.Create(devfile) + if err != nil { + reqLogger.Error(err, fmt.Sprintf("Error creating devfile %v", devfile)) + } + defer f.Close() + _, err = f.WriteString(version.Devfile) + if err != nil { + reqLogger.Error(err, fmt.Sprintf("Error writing devfile %v", devfile)) + } + f.Sync() + + f, err = os.Create(metafile) + if err != nil { + reqLogger.Error(err, fmt.Sprintf("Error creating metafile %v", metafile)) + } + defer f.Close() + _, err = f.WriteString(version.Metafile) + if err != nil { + reqLogger.Error(err, fmt.Sprintf("Error writing metafile %v", metafile)) + } + f.Sync() + } + } + } + + // Regenerate the index + indexfile := fmt.Sprintf("%v/index.json", basePath) + index, err := genIndex(basePath) + if err != nil { + reqLogger.Error(err, "Error generating devfile index") + return reconcile.Result{}, err + } + var b []byte + + if len(index) !=0 { + b, err = json.MarshalIndent(index, "", " ") + if err != nil { + reqLogger.Error(err, "Error during marshal index") + return reconcile.Result{}, err + } + } + + f, err := os.Create(indexfile) + if err != nil { + reqLogger.Error(err, fmt.Sprintf("Error creating indexfile %v", indexfile)) + return reconcile.Result{}, err + } + defer f.Close() + _, err = f.Write(b) + if err != nil { + reqLogger.Error(err, fmt.Sprintf("Error writing indexfile %v", indexfile)) + return reconcile.Result{}, err + } + f.Sync() + + return reconcile.Result{}, nil +} diff --git a/pkg/controller/devfileregistry/index.go b/pkg/controller/devfileregistry/index.go new file mode 100644 index 00000000..8d9ef3b0 --- /dev/null +++ b/pkg/controller/devfileregistry/index.go @@ -0,0 +1,57 @@ +package devfileregistry + +import ( + //"encoding/json" + //"flag" + //"fmt" + "io/ioutil" + //"log" + "path/filepath" + + // Should be odo-devfiles after migration cleanup + // "github.com/odo-devfiles/registry/tools/types" + "github.com/elsony/devfile2-registry/tools/types" + "gopkg.in/yaml.v2" +) + +// genIndex generate new index from meta.yaml files in dir. +// meta.yaml file is expected to be in dir///meta.yaml +func genIndex(dir string) ([]types.MetaIndex, error) { + + var index []types.MetaIndex + + stackdirs, err := ioutil.ReadDir(dir) + if err != nil { + return nil, err + } + + for _, stackdir := range stackdirs { + if stackdir.IsDir() { + versiondirs, err := ioutil.ReadDir(filepath.Join(dir, stackdir.Name())) + if err != nil { + return nil, err + } + for _, versiondir := range versiondirs { + var meta types.Meta + metaFile, err := ioutil.ReadFile(filepath.Join(dir, stackdir.Name(), versiondir.Name(), "meta.yaml")) + if err != nil { + return nil, err + } + err = yaml.Unmarshal(metaFile, &meta) + if err != nil { + return nil, err + } + self := filepath.Join(stackdir.Name(), versiondir.Name(), "devfile.yaml") + metaIndex := types.MetaIndex{ + Meta: meta, + Links: types.Links{ + Self: self, + }, + } + index = append(index, metaIndex) + } + } + } + return index, nil +} + diff --git a/pkg/controller/devfileregistry/register.go b/pkg/controller/devfileregistry/register.go new file mode 100644 index 00000000..8c875069 --- /dev/null +++ b/pkg/controller/devfileregistry/register.go @@ -0,0 +1,6 @@ +package devfileregistry + +func init() { + // AddToManagerFuncs is a list of functions to create controllers and add them to a manager. + AddToManagerFuncs = append(AddToManagerFuncs, Add) +} diff --git a/pkg/controller/kabaneroplatform/devfile-registry.go b/pkg/controller/kabaneroplatform/devfile-registry.go new file mode 100644 index 00000000..c01e6b0d --- /dev/null +++ b/pkg/controller/kabaneroplatform/devfile-registry.go @@ -0,0 +1,150 @@ +package kabaneroplatform + +import ( + "context" + "github.com/go-logr/logr" + kabanerov1alpha2 "github.com/kabanero-io/kabanero-operator/pkg/apis/kabanero/v1alpha2" + "github.com/kabanero-io/kabanero-operator/pkg/versioning" + + corev1 "k8s.io/api/core/v1" + + mf "github.com/manifestival/manifestival" + mfc "github.com/manifestival/controller-runtime-client" + "k8s.io/apimachinery/pkg/api/errors" + "sigs.k8s.io/controller-runtime/pkg/client" + "strings" +) + +func reconcileDevfileRegistry(ctx context.Context, k *kabanerov1alpha2.Kabanero, c client.Client, reqLogger logr.Logger) error { + + // Figure out what version of the orchestration we are going to use. If this version doesn't have a devfile + // registry, then make sure the devfile registry is stopped. + rev, err := resolveSoftwareRevision(k, "devfile-registry-controller", k.Spec.DevfileRegistry.Version) + if err != nil { + // We'll need to re-evaluate this down the road, but for now assume the 0.10.0 version of the devfile + // registry controller might be installed, so lets try to remove it. + rev, err2 := resolveSoftwareRevision(k, "devfile-registry-controller", "0.10.0") + if err2 != nil { + return err + } + + err = cleanupDevfileRegistryForRevision(rev, k, c, reqLogger) + if err != nil { + return err + } + + return nil + } + + templateContext := rev.Identifiers + + image, err := imageUriWithOverrides(k.Spec.DevfileRegistry.Repository, k.Spec.DevfileRegistry.Tag, k.Spec.DevfileRegistry.Image, rev) + if err != nil { + return err + } + + templateContext["image"] = image + templateContext["instance"] = k.ObjectMeta.UID + templateContext["version"] = rev.Version + + f, err := rev.OpenOrchestration("devfile-registry-controller.yaml") + if err != nil { + return err + } + + s, err := renderOrchestration(f, templateContext) + if err != nil { + return err + } + + mOrig, err := mf.ManifestFrom(mf.Reader(strings.NewReader(s)), mf.UseClient(mfc.NewClient(c)), mf.UseLogger(reqLogger.WithName("manifestival"))) + if err != nil { + return err + } + + transforms := []mf.Transformer{ + mf.InjectOwner(k), + mf.InjectNamespace(k.GetNamespace()), + } + + m, err := mOrig.Transform(transforms...) + if err != nil { + return err + } + + err = m.Apply() + if err != nil { + return err + } + + return nil +} + + +func cleanupDevfileRegistry(k *kabanerov1alpha2.Kabanero, c client.Client, reqLogger logr.Logger) error { + + rev, err := resolveSoftwareRevision(k, "devfile-registry-controller", k.Spec.DevfileRegistry.Version) + if err != nil { + // It may be that this version of kabanero doesn't have a devfile registry, so just exit. + reqLogger.Error(err, "Error encountered when cleanup up the devfile registry") + return nil + } + + return cleanupDevfileRegistryForRevision(rev, k, c, reqLogger) +} + +func cleanupDevfileRegistryForRevision(rev versioning.SoftwareRevision, k *kabanerov1alpha2.Kabanero, c client.Client, reqLogger logr.Logger) error { + + //The context which will be used to render any templates + templateContext := rev.Identifiers + + // TODO + image, err := imageUriWithOverrides(k.Spec.DevfileRegistry.Repository, k.Spec.DevfileRegistry.Tag, k.Spec.DevfileRegistry.Image, rev) + if err != nil { + return err + } + templateContext["image"] = image + templateContext["instance"] = k.ObjectMeta.UID + templateContext["version"] = rev.Version + + f, err := rev.OpenOrchestration("devfile-registry-controller.yaml") + if err != nil { + return err + } + + s, err := renderOrchestration(f, templateContext) + if err != nil { + return err + } + + mOrig, err := mf.ManifestFrom(mf.Reader(strings.NewReader(s)), mf.UseClient(mfc.NewClient(c)), mf.UseLogger(reqLogger.WithName("manifestival"))) + if err != nil { + return err + } + + transforms := []mf.Transformer{mf.InjectNamespace(k.GetNamespace())} + m, err := mOrig.Transform(transforms...) + if err != nil { + return err + } + + // Manifestival ignores the "NotFound" error for us. + err = m.Delete() + if err != nil { + return err + } + + + // Now, clean up the things that the controller-runtime created on + // our behalf. + secretInstance := &corev1.Secret{} + secretInstance.Name = "kabanero-operator-devfile-registry-cert" + secretInstance.Namespace = k.GetNamespace() + err = c.Delete(context.TODO(), secretInstance) + + if (err != nil) && (errors.IsNotFound(err) == false) { + return err + } + + return nil +} diff --git a/pkg/controller/kabaneroplatform/kabaneroplatform_controller.go b/pkg/controller/kabaneroplatform/kabaneroplatform_controller.go index 20fc6407..a17bdd64 100644 --- a/pkg/controller/kabaneroplatform/kabaneroplatform_controller.go +++ b/pkg/controller/kabaneroplatform/kabaneroplatform_controller.go @@ -10,7 +10,7 @@ import ( "github.com/go-logr/logr" kabanerov1alpha2 "github.com/kabanero-io/kabanero-operator/pkg/apis/kabanero/v1alpha2" - "github.com/kabanero-io/kabanero-operator/pkg/controller/utils/timer" + "github.com/kabanero-io/kabanero-operator/pkg/controller/utils/timer" "github.com/kabanero-io/kabanero-operator/pkg/versioning" mfc "github.com/manifestival/controller-runtime-client" mf "github.com/manifestival/manifestival" @@ -56,6 +56,7 @@ var reconcileFuncs = []reconcileFuncType{ {name: "sso", function: reconcileSso}, {name: "gitops", function: reconcileGitopsPipelines}, {name: "target namespaces", function: reconcileTargetNamespaces}, + {name: "devfile registry controller", function: reconcileDevfileRegistry}, } // Add creates a new Kabanero Controller and adds it to the Manager. The Manager will set fields on the Controller @@ -65,7 +66,7 @@ func Add(mgr manager.Manager) error { watchNamespace, err := k8sutil.GetWatchNamespace() if err != nil { return err - } +} // Lets be sure a single namespace is specified. numberOfWatchNamespaces := len(strings.Split(watchNamespace, ",")) @@ -140,7 +141,7 @@ func (r *ReconcileKabanero) getOperatorImage() (string, error) { if len(podName) == 0 { return "", fmt.Errorf("The POD_NAME environment variable is not set, or is empty") } - + // Second, get the Pod instance with that name pod := &corev1.Pod{} kubePodName := types.NamespacedName{Name: podName, Namespace: r.watchNamespace} @@ -495,13 +496,19 @@ func cleanup(ctx context.Context, k *kabanerov1alpha2.Kabanero, client client.Cl if err != nil { return err } - + // Remove the cross-namespace objects that target namespaces use. err = cleanupTargetNamespaces(ctx, k, client) if err != nil { return err } + // Cleanup the Devfile registry controller and its cross-namespace objects + err = cleanupDevfileRegistry(k, client, reqLogger) + if err != nil { + return err + } + return nil }