From d596d8dd27e48e991ad59c8b32c2baa7ed16a43f Mon Sep 17 00:00:00 2001 From: Daniel Cleyrat Date: Tue, 2 Jun 2020 11:28:46 -0400 Subject: [PATCH 1/7] serving for devfile --- Makefile | 4 +- build/Dockerfile | 3 + cmd/serving/index.json | 66 +++++++++ cmd/serving/main.go | 17 +++ .../orchestrations/serving/0.1/serving.yaml | 80 +++++++++++ config/samples/full.yaml | 11 ++ config/versions.yaml | 5 + deploy/crds/kabanero.io_kabaneros_crd.yaml | 11 ++ pkg/apis/kabanero/v1alpha2/kabanero_types.go | 9 ++ .../v1alpha2/zz_generated.deepcopy.go | 17 +++ .../kabaneroplatform_controller.go | 9 +- pkg/controller/kabaneroplatform/serving.go | 129 ++++++++++++++++++ 12 files changed, 359 insertions(+), 2 deletions(-) create mode 100644 cmd/serving/index.json create mode 100644 cmd/serving/main.go create mode 100644 config/orchestrations/serving/0.1/serving.yaml create mode 100644 pkg/controller/kabaneroplatform/serving.go diff --git a/Makefile b/Makefile index 46e7ff0d..19e7b0b4 100755 --- a/Makefile +++ b/Makefile @@ -73,6 +73,7 @@ build: generate GO111MODULE=on go install ./cmd/manager/collection GO111MODULE=on go install ./cmd/manager/stack GO111MODULE=on go install ./cmd/admission-webhook + GO111MODULE=on go install ./cmd/serving build-image: generate # These commands were taken from operator-sdk 0.8.1. The sdk did not let us @@ -83,6 +84,7 @@ build-image: generate GO111MODULE=on CGO_ENABLED=0 GOOS=linux GOARCH=$(ARCH) go build -o build/_output/bin/kabanero-operator-collection-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/collection 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/serving -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/serving docker build -f build/Dockerfile -t $(IMAGE) . @@ -197,7 +199,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-collection-controller 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-collection-controller build/_output/bin/kabanero-operator-stack-controller build/_output/bin/serving | sort > 3RD_PARTY || true rm build/bin/golicense # Integration Tests diff --git a/build/Dockerfile b/build/Dockerfile index 659be6eb..d2cb10a7 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -18,6 +18,9 @@ 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/kabanero-operator-collection-controller /usr/local/bin/kabanero-operator-collection-controller COPY build/_output/bin/admission-webhook /usr/local/bin/admission-webhook +COPY build/_output/bin/serving /usr/local/bin/serving + +COPY cmd/serving/index.json /devfiles/index.json COPY build/bin /usr/local/bin RUN /usr/local/bin/user_setup diff --git a/cmd/serving/index.json b/cmd/serving/index.json new file mode 100644 index 00000000..6bf9abf9 --- /dev/null +++ b/cmd/serving/index.json @@ -0,0 +1,66 @@ +[ + { + "displayName": "Maven Java", + "description": "Upstream Maven and OpenJDK 11", + "tags": [ + "Java", + "Maven" + ], + "projectType": "maven", + "language": "java", + "links": { + "self": "/devfiles/eclipse/maven/1.0.0/devfile.yaml" + } + }, + { + "displayName": "NodeJS Express Web Application", + "description": "Stack with NodeJS 10", + "tags": [ + "NodeJS", + "Express", + "ubi8" + ], + "projectType": "nodejs", + "language": "nodejs", + "links": { + "self": "/devfiles/eclipse/nodejs/1.0.0/devfile.yaml" + } + }, + { + "displayName": "Open Liberty", + "description": "Open Liberty microservice in Java", + "projectType": "docker", + "language": "java", + "links": { + "self": "/devfiles/eclipse/openLiberty/1.0.0/devfile.yaml" + } + }, + { + "displayName": "Spring Boot®", + "description": "Spring Boot® using Java", + "tags": [ + "Java", + "Spring" + ], + "globalMemoryLimit": "2674Mi", + "icon": "https://www.eclipse.org/che/images/logo-eclipseche.svg", + "projectType": "spring", + "language": "java", + "links": { + "self": "/devfiles/eclipse/springBoot/1.0.0/devfile.yaml" + } + }, + { + "displayName": "Quarkus Java", + "description": "Upstream Quarkus with Java+GraalVM", + "tags": [ + "Java", + "Quarkus" + ], + "projectType": "quarkus", + "language": "java", + "links": { + "self": "/devfiles/eclipse/quarkus/1.0.0/devfile.yaml" + } + } +] diff --git a/cmd/serving/main.go b/cmd/serving/main.go new file mode 100644 index 00000000..ce33ed0b --- /dev/null +++ b/cmd/serving/main.go @@ -0,0 +1,17 @@ +package main + +import ( + "log" + "net/http" +) + +func response(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Header().Set("Content-Type", "application/json") + http.ServeFile(w, r, "/devfiles/index.json") +} + +func main() { + http.HandleFunc("/",response) + log.Fatal(http.ListenAndServeTLS(":443","/tmp/serving-certs/tls.crt","/tmp/serving-certs/tls.key",nil)) +} diff --git a/config/orchestrations/serving/0.1/serving.yaml b/config/orchestrations/serving/0.1/serving.yaml new file mode 100644 index 00000000..28da9a24 --- /dev/null +++ b/config/orchestrations/serving/0.1/serving.yaml @@ -0,0 +1,80 @@ +apiVersion: v1 +kind: Service +metadata: + name: kabanero-operator-serving + annotations: + service.beta.openshift.io/serving-cert-secret-name: kabanero-operator-serving-cert + labels: + app.kubernetes.io/name: kabanero-operator-serving + app.kubernetes.io/instance: {{ .instance }} + app.kubernetes.io/version: {{ .version }} + app.kubernetes.io/component: serving + app.kubernetes.io/part-of: kabanero + app.kubernetes.io/managed-by: kabanero-operator +spec: + selector: + name: kabanero-operator-serving + ports: + - protocol: TCP + port: 443 + targetPort: 8443 +--- +apiVersion: route.openshift.io/v1 +kind: Route +metadata: + name: kabanero-operator-serving +spec: + to: + kind: Service + name: kabanero-operator-serving + tls: + termination: reencrypt + insecureEdgeTerminationPolicy: Redirect +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: kabanero-operator-serving + labels: + name: kabanero-operator-serving + app.kubernetes.io/name: kabanero-operator-serving + app.kubernetes.io/instance: {{ .instance }} + app.kubernetes.io/version: {{ .version }} + app.kubernetes.io/component: serving + app.kubernetes.io/part-of: kabanero + app.kubernetes.io/managed-by: kabanero-operator +spec: + replicas: 1 + selector: + matchLabels: + name: kabanero-operator-serving + template: + metadata: + labels: + name: kabanero-operator-serving + app.kubernetes.io/name: kabanero-operator-serving + app.kubernetes.io/instance: {{ .instance }} + app.kubernetes.io/version: {{ .version }} + app.kubernetes.io/component: serving + app.kubernetes.io/part-of: kabanero + app.kubernetes.io/managed-by: kabanero-operator + spec: + containers: + - name: kabanero-operator-serving + image: {{ .image }} + imagePullPolicy: Always + command: + - /usr/local/bin/serving + env: + - name: KABANERO_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + volumeMounts: + - mountPath: /tmp/serving-certs + name: kabanero-operator-serving-cert + readOnly: true + volumes: + - name: kabanero-operator-serving-cert + secret: + secretName: kabanero-operator-serving-cert diff --git a/config/samples/full.yaml b/config/samples/full.yaml index 1570e13c..ccfb2bcf 100644 --- a/config/samples/full.yaml +++ b/config/samples/full.yaml @@ -77,6 +77,17 @@ spec: # Overrides the image uri image: kabanero/kabanero-operator:TRAVIS_TAG + + serving: + # Overrides the setting for version on this component + version: "0.9.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 fb617f4e..7c5ec0c5 100644 --- a/config/versions.yaml +++ b/config/versions.yaml @@ -23,6 +23,7 @@ kabanero: admission-webhook: "0.9.0" sso: "7.3.2" codeready-workspaces: "0.9.0" + serving: "0.9.0" - version: "0.8.0" related-versions: @@ -231,3 +232,7 @@ related-software: sso: - version: "7.3.2" orchestrations: "orchestrations/sso/0.1" + + serving: + - version: "0.9.0" + orchestrations: "orchestrations/serving/0.1" diff --git a/deploy/crds/kabanero.io_kabaneros_crd.yaml b/deploy/crds/kabanero.io_kabaneros_crd.yaml index bbb58bb7..15c51d82 100644 --- a/deploy/crds/kabanero.io_kabaneros_crd.yaml +++ b/deploy/crds/kabanero.io_kabaneros_crd.yaml @@ -549,6 +549,17 @@ spec: version: type: string type: object + serving: + properties: + image: + type: string + repository: + type: string + tag: + type: string + version: + type: string + type: object sso: properties: adminSecretName: diff --git a/pkg/apis/kabanero/v1alpha2/kabanero_types.go b/pkg/apis/kabanero/v1alpha2/kabanero_types.go index a7155875..a6945e92 100644 --- a/pkg/apis/kabanero/v1alpha2/kabanero_types.go +++ b/pkg/apis/kabanero/v1alpha2/kabanero_types.go @@ -45,6 +45,8 @@ type KabaneroSpec struct { AdmissionControllerWebhook AdmissionControllerWebhookCustomizationSpec `json:"admissionControllerWebhook,omitempty"` + Serving ServingSpec `json:"serving,omitempty"` + Sso SsoCustomizationSpec `json:"sso,omitempty"` Gitops GitopsSpec `json:"gitops,omitempty"` @@ -232,6 +234,13 @@ type AdmissionControllerWebhookCustomizationSpec struct { Tag string `json:"tag,omitempty"` } +type ServingSpec 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"` diff --git a/pkg/apis/kabanero/v1alpha2/zz_generated.deepcopy.go b/pkg/apis/kabanero/v1alpha2/zz_generated.deepcopy.go index 5959947a..e60dadeb 100644 --- a/pkg/apis/kabanero/v1alpha2/zz_generated.deepcopy.go +++ b/pkg/apis/kabanero/v1alpha2/zz_generated.deepcopy.go @@ -647,6 +647,7 @@ func (in *KabaneroSpec) DeepCopyInto(out *KabaneroSpec) { out.CollectionController = in.CollectionController out.StackController = in.StackController out.AdmissionControllerWebhook = in.AdmissionControllerWebhook + out.Serving = in.Serving out.Sso = in.Sso in.Gitops.DeepCopyInto(&out.Gitops) return @@ -846,6 +847,22 @@ func (in *ServerlessStatus) DeepCopy() *ServerlessStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServingSpec) DeepCopyInto(out *ServingSpec) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServingSpec. +func (in *ServingSpec) DeepCopy() *ServingSpec { + if in == nil { + return nil + } + out := new(ServingSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SsoCustomizationSpec) DeepCopyInto(out *SsoCustomizationSpec) { *out = *in diff --git a/pkg/controller/kabaneroplatform/kabaneroplatform_controller.go b/pkg/controller/kabaneroplatform/kabaneroplatform_controller.go index 34961147..439ca019 100644 --- a/pkg/controller/kabaneroplatform/kabaneroplatform_controller.go +++ b/pkg/controller/kabaneroplatform/kabaneroplatform_controller.go @@ -54,6 +54,7 @@ var reconcileFuncs = []reconcileFuncType{ {name: "events", function: reconcileEvents}, {name: "sso", function: reconcileSso}, {name: "gitops", function: reconcileGitopsPipelines}, + {name: "serving", function: reconcileServing}, } // Add creates a new Kabanero Controller and adds it to the Manager. The Manager will set fields on the Controller @@ -515,7 +516,13 @@ func cleanup(ctx context.Context, k *kabanerov1alpha2.Kabanero, client client.Cl if err != nil { return err } - + + // Cleanup the Serving and their cross-namespace objects + err = cleanupServing(k, client, reqLogger) + if err != nil { + return err + } + return nil } diff --git a/pkg/controller/kabaneroplatform/serving.go b/pkg/controller/kabaneroplatform/serving.go new file mode 100644 index 00000000..d560bcba --- /dev/null +++ b/pkg/controller/kabaneroplatform/serving.go @@ -0,0 +1,129 @@ +package kabaneroplatform + +import ( + "context" + "github.com/go-logr/logr" + kabanerov1alpha2 "github.com/kabanero-io/kabanero-operator/pkg/apis/kabanero/v1alpha2" + + 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 reconcileServing(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. + rev, err := resolveSoftwareRevision(k, "serving", k.Spec.Serving.Version) + if err != nil { + return err + } + + templateContext := rev.Identifiers + + image, err := imageUriWithOverrides(k.Spec.Serving.Repository, k.Spec.Serving.Tag, k.Spec.Serving.Image, rev) + if err != nil { + return err + } + + templateContext["image"] = image + templateContext["instance"] = k.ObjectMeta.UID + templateContext["version"] = rev.Version + + f, err := rev.OpenOrchestration("serving.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 cleanupServing(k *kabanerov1alpha2.Kabanero, c client.Client, reqLogger logr.Logger) error { + + rev, err := resolveSoftwareRevision(k, "serving", k.Spec.Serving.Version) + if err != nil { + return err + } + + //The context which will be used to render any templates + templateContext := rev.Identifiers + + // TODO + image, err := imageUriWithOverrides(k.Spec.Serving.Repository, k.Spec.Serving.Tag, k.Spec.Serving.Image, rev) + if err != nil { + return err + } + templateContext["image"] = image + templateContext["instance"] = k.ObjectMeta.UID + templateContext["version"] = rev.Version + + f, err := rev.OpenOrchestration("serving.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-serving-cert" + secretInstance.Namespace = k.GetNamespace() + err = c.Delete(context.TODO(), secretInstance) + + if (err != nil) && (errors.IsNotFound(err) == false) { + return err + } + + return nil +} From 09ba3df63d2cfadc3e6ebf5684b993a3e13d8abd Mon Sep 17 00:00:00 2001 From: Daniel Cleyrat Date: Tue, 2 Jun 2020 14:47:04 -0400 Subject: [PATCH 2/7] serving for devfile --- cmd/serving/main.go | 8 +++++++- config/versions.yaml | 3 +++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/cmd/serving/main.go b/cmd/serving/main.go index ce33ed0b..60253d37 100644 --- a/cmd/serving/main.go +++ b/cmd/serving/main.go @@ -13,5 +13,11 @@ func response(w http.ResponseWriter, r *http.Request) { func main() { http.HandleFunc("/",response) - log.Fatal(http.ListenAndServeTLS(":443","/tmp/serving-certs/tls.crt","/tmp/serving-certs/tls.key",nil)) + log.Fatal(http.ListenAndServeTLS(":8443","/tmp/serving-certs/tls.crt","/tmp/serving-certs/tls.key",nil)) } + +// Watch Stacks + +// Build registry from Stack spec + +// Regenerate index.json diff --git a/config/versions.yaml b/config/versions.yaml index 7c5ec0c5..4a3b378e 100644 --- a/config/versions.yaml +++ b/config/versions.yaml @@ -236,3 +236,6 @@ related-software: serving: - version: "0.9.0" orchestrations: "orchestrations/serving/0.1" + identifiers: + repository: "FROM_POD" + tag: "FROM_POD" From 83f4b78f84923467b42a6fcf23ed6a8c47c4e3c9 Mon Sep 17 00:00:00 2001 From: Daniel Cleyrat Date: Wed, 3 Jun 2020 13:40:06 -0400 Subject: [PATCH 3/7] serving --- build/Dockerfile | 1 - cmd/serving/index.json | 66 ------- cmd/serving/main.go | 218 ++++++++++++++++++++-- go.sum | 2 + pkg/apis/kabanero/v1alpha2/stack_types.go | 4 +- pkg/serving/index.go | 80 ++++++++ pkg/serving/stack_controller.go | 145 ++++++++++++++ 7 files changed, 437 insertions(+), 79 deletions(-) delete mode 100644 cmd/serving/index.json create mode 100644 pkg/serving/index.go create mode 100644 pkg/serving/stack_controller.go diff --git a/build/Dockerfile b/build/Dockerfile index d2cb10a7..e8df32f9 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -20,7 +20,6 @@ COPY build/_output/bin/kabanero-operator-collection-controller /usr/local/bin/ka COPY build/_output/bin/admission-webhook /usr/local/bin/admission-webhook COPY build/_output/bin/serving /usr/local/bin/serving -COPY cmd/serving/index.json /devfiles/index.json COPY build/bin /usr/local/bin RUN /usr/local/bin/user_setup diff --git a/cmd/serving/index.json b/cmd/serving/index.json deleted file mode 100644 index 6bf9abf9..00000000 --- a/cmd/serving/index.json +++ /dev/null @@ -1,66 +0,0 @@ -[ - { - "displayName": "Maven Java", - "description": "Upstream Maven and OpenJDK 11", - "tags": [ - "Java", - "Maven" - ], - "projectType": "maven", - "language": "java", - "links": { - "self": "/devfiles/eclipse/maven/1.0.0/devfile.yaml" - } - }, - { - "displayName": "NodeJS Express Web Application", - "description": "Stack with NodeJS 10", - "tags": [ - "NodeJS", - "Express", - "ubi8" - ], - "projectType": "nodejs", - "language": "nodejs", - "links": { - "self": "/devfiles/eclipse/nodejs/1.0.0/devfile.yaml" - } - }, - { - "displayName": "Open Liberty", - "description": "Open Liberty microservice in Java", - "projectType": "docker", - "language": "java", - "links": { - "self": "/devfiles/eclipse/openLiberty/1.0.0/devfile.yaml" - } - }, - { - "displayName": "Spring Boot®", - "description": "Spring Boot® using Java", - "tags": [ - "Java", - "Spring" - ], - "globalMemoryLimit": "2674Mi", - "icon": "https://www.eclipse.org/che/images/logo-eclipseche.svg", - "projectType": "spring", - "language": "java", - "links": { - "self": "/devfiles/eclipse/springBoot/1.0.0/devfile.yaml" - } - }, - { - "displayName": "Quarkus Java", - "description": "Upstream Quarkus with Java+GraalVM", - "tags": [ - "Java", - "Quarkus" - ], - "projectType": "quarkus", - "language": "java", - "links": { - "self": "/devfiles/eclipse/quarkus/1.0.0/devfile.yaml" - } - } -] diff --git a/cmd/serving/main.go b/cmd/serving/main.go index 60253d37..92ba41a8 100644 --- a/cmd/serving/main.go +++ b/cmd/serving/main.go @@ -1,23 +1,219 @@ package main import ( - "log" - "net/http" + "context" + "errors" + "flag" + "fmt" + "os" + "runtime" + "strings" + + // 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/serving" + + "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 response(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - w.Header().Set("Content-Type", "application/json") - http.ServeFile(w, r, "/devfiles/index.json") +func printVersion() { + log.Info(fmt.Sprintf("Operator Version: %s", version.Version)) + log.Info(fmt.Sprintf("Go Version: %s", runtime.Version())) + log.Info(fmt.Sprintf("Go OS/Arch: %s/%s", runtime.GOOS, runtime.GOARCH)) + log.Info(fmt.Sprintf("Version of operator-sdk: %v", sdkVersion.Version)) } func main() { - http.HandleFunc("/",response) - log.Fatal(http.ListenAndServeTLS(":8443","/tmp/serving-certs/tls.crt","/tmp/serving-certs/tls.key",nil)) + // 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()) + + printVersion() + + 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, "memcached-operator-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 := controller.AddToManager(mgr); err != nil { + log.Error(err, "") + os.Exit(1) + } + + // Add the Metrics Service + addMetrics(ctx, cfg) + + // Start serving devfiles index + fs := http.FileServer(http.Dir("/devfiles")) + http.Handle("/", fs) + log.Println("Starting Devfile registry on port :8443...") + err := http.ListenAndServeTLS(":8443","/tmp/serving-certs/tls.crt","/tmp/serving-certs/tls.key",nil) + if err != nil { + log.Fatal(err) + } + + log.Info("Starting the Cmd.") + + // Start the Cmd + if err := mgr.Start(signals.SetupSignalHandler()); err != nil { + log.Error(err, "Manager exited non-zero") + os.Exit(1) + } } -// Watch Stacks +// 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()) + } -// Build registry from Stack spec + // 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}}, + } -// Regenerate index.json + // 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/go.sum b/go.sum index 68c7791c..cb31508c 100644 --- a/go.sum +++ b/go.sum @@ -249,6 +249,8 @@ github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1 github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/elsony/devfile2-registry v0.0.0-20200528212517-f1b6f7216872 h1:XnJh44fe+mEAZ5xs3CIP2DVumJ+7OHF2irFUA9ut0uc= +github.com/elsony/devfile2-registry/tools v0.0.0-20200528212517-f1b6f7216872 h1:s3/e9/JXjx65H8RJvAbbEDJtnDtiYPlRlvWl8F92ffE= 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/v1alpha2/stack_types.go b/pkg/apis/kabanero/v1alpha2/stack_types.go index b90faf18..7b84edd5 100644 --- a/pkg/apis/kabanero/v1alpha2/stack_types.go +++ b/pkg/apis/kabanero/v1alpha2/stack_types.go @@ -61,7 +61,9 @@ type StackVersion struct { DesiredState string `json:"desiredState,omitempty"` SkipCertVerification bool `json:"skipCertVerification,omitempty"` // +listType=set - Images []Image `json:"images,omitempty"` + Images []Image `json:"images,omitempty"` + Devfile string `json:"devfile,omitempty"` + Metafile string `json:"metafile,omitempty"` } func (sv StackVersion) GetVersion() string { diff --git a/pkg/serving/index.go b/pkg/serving/index.go new file mode 100644 index 00000000..49742faf --- /dev/null +++ b/pkg/serving/index.go @@ -0,0 +1,80 @@ +package main + +import ( + "encoding/json" + "flag" + "fmt" + "io/ioutil" + "log" + "path/filepath" + + "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 + + dirs, err := ioutil.ReadDir(dir) + if err != nil { + return nil, err + } + + for _, file := range dirs { + if file.IsDir() { + var meta types.Meta + metaFile, err := ioutil.ReadFile(filepath.Join(dir, file.Name(), "meta.yaml")) + if err != nil { + return nil, err + } + err = yaml.Unmarshal(metaFile, &meta) + if err != nil { + return nil, err + } + + self := fmt.Sprintf("/%s/%s/%s", filepath.Base(dir), file.Name(), "devfile.yaml") + + metaIndex := types.MetaIndex{ + Meta: meta, + Links: types.Links{ + Self: self, + }, + } + index = append(index, metaIndex) + } + } + return index, nil +} + +func main() { + devfiles := flag.String("devfiles-dir", "", "Directory containing devfiles.") + output := flag.String("index", "", "Index filaname. This is where the index in JSON format will be saved.") + + flag.Parse() + + if *devfiles == "" { + log.Fatal("Provide devfile directory.") + } + + if *output == "" { + log.Fatal("Provide index file.") + } + + index, err := genIndex(*devfiles) + if err != nil { + log.Fatal(err) + } + b, err := json.MarshalIndent(index, "", " ") + if err != nil { + log.Fatal(err) + + } + err = ioutil.WriteFile(*output, b, 0644) + if err != nil { + log.Fatal(err) + + } +} diff --git a/pkg/serving/stack_controller.go b/pkg/serving/stack_controller.go new file mode 100644 index 00000000..e2daa8c5 --- /dev/null +++ b/pkg/serving/stack_controller.go @@ -0,0 +1,145 @@ +package stack + +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" + + d2index "github.com/elsony/devfile2-registry/tools/cmd/index" +) + +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 + } + + // Copy the yaml from the Stack to local devfile registry + for _, version := range instance.Spec.Versions { + + basePath := "/devfiles" + devfile := fmt.Sprintf("%v/eclipse/%v/%v/devfile.yaml", basePath, instance.ObjectMeta.Name, version.Version) + metafile := fmt.Sprintf("%v/eclipse/%v/%v/meta.yaml", basePath, instance.ObjectMeta.Name, version.Version) + + 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.Devfile) + 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 := d2index.genIndex(basePath) + if err != nil { + reqLogger.Error(err, "Error generating devfile index") + } + b, err := json.MarshalIndent(index, "", " ") + if err != nil { + reqLogger.Error(err, "Error during marshal index") + } + f, err = os.Create(indexfile) + if err != nil { + reqLogger.Error(err, fmt.Sprintf("Error creating indexfile %v", indexfile)) + } + defer f.Close() + _, err = f.Write(b) + if err != nil { + reqLogger.Error(err, fmt.Sprintf("Error writing indexfile %v", metafile)) + } + f.Sync() + + return reconcile.Result{}, nil +} From edcee004092be30ab57446c879bfe63754a8495a Mon Sep 17 00:00:00 2001 From: Daniel Cleyrat Date: Thu, 4 Jun 2020 15:53:59 -0400 Subject: [PATCH 4/7] devfile2 --- build/Dockerfile | 1 + cmd/serving/main.go | 65 ++++++----- .../orchestrations/serving/0.1/serving.yaml | 101 +++++++++++++++++- deploy/crds/kabanero.io_stacks_crd.yaml | 4 + go.mod | 3 +- go.sum | 5 + pkg/controller/serving/controller.go | 18 ++++ pkg/controller/serving/index.go | 57 ++++++++++ pkg/controller/serving/register.go | 6 ++ .../serving/serving_controller.go} | 93 ++++++++++------ pkg/serving/index.go | 80 -------------- 11 files changed, 287 insertions(+), 146 deletions(-) create mode 100644 pkg/controller/serving/controller.go create mode 100644 pkg/controller/serving/index.go create mode 100644 pkg/controller/serving/register.go rename pkg/{serving/stack_controller.go => controller/serving/serving_controller.go} (65%) delete mode 100644 pkg/serving/index.go diff --git a/build/Dockerfile b/build/Dockerfile index e8df32f9..c6ae79c1 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -20,6 +20,7 @@ COPY build/_output/bin/kabanero-operator-collection-controller /usr/local/bin/ka COPY build/_output/bin/admission-webhook /usr/local/bin/admission-webhook COPY build/_output/bin/serving /usr/local/bin/serving +RUN mkdir /devfiles && chmod +777 /devfiles COPY build/bin /usr/local/bin RUN /usr/local/bin/user_setup diff --git a/cmd/serving/main.go b/cmd/serving/main.go index 92ba41a8..e7cdffb0 100644 --- a/cmd/serving/main.go +++ b/cmd/serving/main.go @@ -6,22 +6,23 @@ import ( "flag" "fmt" "os" - "runtime" + // "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/serving" + "github.com/kabanero-io/kabanero-operator/pkg/controller/serving" "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" + // sdkVersion "github.com/operator-framework/operator-sdk/version" "github.com/spf13/pflag" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/intstr" @@ -40,13 +41,6 @@ var ( ) var log = logf.Log.WithName("cmd") -func printVersion() { - log.Info(fmt.Sprintf("Operator Version: %s", version.Version)) - log.Info(fmt.Sprintf("Go Version: %s", runtime.Version())) - log.Info(fmt.Sprintf("Go OS/Arch: %s/%s", runtime.GOOS, runtime.GOARCH)) - log.Info(fmt.Sprintf("Version of operator-sdk: %v", sdkVersion.Version)) -} - func main() { // Add the zap logger flag set to the CLI. The flag set must // be added before calling pflag.Parse(). @@ -68,8 +62,6 @@ func main() { // uniform and structured logs. logf.SetLogger(zap.Logger()) - printVersion() - namespace, err := k8sutil.GetWatchNamespace() if err != nil { log.Error(err, "Failed to get watch namespace") @@ -85,7 +77,7 @@ func main() { ctx := context.TODO() // Become the leader before proceeding - err = leader.Become(ctx, "memcached-operator-lock") + err = leader.Become(ctx, "serving-lock") if err != nil { log.Error(err, "") os.Exit(1) @@ -122,7 +114,7 @@ func main() { } // Setup all Controllers - if err := controller.AddToManager(mgr); err != nil { + if err := serving.AddToManager(mgr); err != nil { log.Error(err, "") os.Exit(1) } @@ -130,24 +122,45 @@ func main() { // Add the Metrics Service addMetrics(ctx, cfg) - // Start serving devfiles index - fs := http.FileServer(http.Dir("/devfiles")) - http.Handle("/", fs) - log.Println("Starting Devfile registry on port :8443...") - err := http.ListenAndServeTLS(":8443","/tmp/serving-certs/tls.crt","/tmp/serving-certs/tls.key",nil) - if err != nil { - log.Fatal(err) - } - - log.Info("Starting the Cmd.") + //Start HTTTPS & Cmd + errs := Run(mgr) - // Start the Cmd - if err := mgr.Start(signals.SetupSignalHandler()); err != nil { + // 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) { diff --git a/config/orchestrations/serving/0.1/serving.yaml b/config/orchestrations/serving/0.1/serving.yaml index 28da9a24..ddba8738 100644 --- a/config/orchestrations/serving/0.1/serving.yaml +++ b/config/orchestrations/serving/0.1/serving.yaml @@ -19,6 +19,92 @@ spec: port: 443 targetPort: 8443 --- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + creationTimestamp: null + name: kabanero-operator-serving +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-serving +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: kabanero-operator-serving +subjects: +- kind: ServiceAccount + name: kabanero-operator-serving +roleRef: + kind: Role + name: kabanero-operator-serving + apiGroup: rbac.authorization.k8s.io +--- apiVersion: route.openshift.io/v1 kind: Route metadata: @@ -59,6 +145,7 @@ spec: app.kubernetes.io/part-of: kabanero app.kubernetes.io/managed-by: kabanero-operator spec: + serviceAccount: kabanero-operator-serving containers: - name: kabanero-operator-serving image: {{ .image }} @@ -66,10 +153,16 @@ spec: command: - /usr/local/bin/serving env: - - name: KABANERO_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace + - name: WATCH_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: OPERATOR_NAME + value: "kabanero-operator-serving" volumeMounts: - mountPath: /tmp/serving-certs name: kabanero-operator-serving-cert diff --git a/deploy/crds/kabanero.io_stacks_crd.yaml b/deploy/crds/kabanero.io_stacks_crd.yaml index 854aa37d..547dcc64 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,6 +63,8 @@ spec: type: string type: object type: array + metafile: + type: string pipelines: items: description: PipelineSpec defines a set of pipelines and associated diff --git a/go.mod b/go.mod index c1c9a1cd..37d9c9ca 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017 github.com/docker/distribution v2.7.1+incompatible github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7 + 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.4 github.com/google/go-cmp v0.3.1 @@ -26,7 +27,7 @@ require ( github.com/tektoncd/operator v0.0.0-20191017104520-be5a46fc149a github.com/tektoncd/pipeline v0.10.1 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 - gopkg.in/yaml.v2 v2.2.5 + gopkg.in/yaml.v2 v2.2.8 k8s.io/api v0.17.0 k8s.io/apiextensions-apiserver v0.0.0 k8s.io/apimachinery v0.17.1 diff --git a/go.sum b/go.sum index cb31508c..4f17e9b6 100644 --- a/go.sum +++ b/go.sum @@ -250,7 +250,10 @@ github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFP github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/elsony/devfile2-registry v0.0.0-20200528212517-f1b6f7216872 h1:XnJh44fe+mEAZ5xs3CIP2DVumJ+7OHF2irFUA9ut0uc= +github.com/elsony/devfile2-registry v0.0.0-20200603181527-db339ef8dd30 h1:zUCwZrhcWRrYId4yDe56RvWD2V+juIve52z3kvcV45Q= github.com/elsony/devfile2-registry/tools v0.0.0-20200528212517-f1b6f7216872 h1:s3/e9/JXjx65H8RJvAbbEDJtnDtiYPlRlvWl8F92ffE= +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= @@ -1149,6 +1152,8 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20190905181640-827449938966/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.1.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= diff --git a/pkg/controller/serving/controller.go b/pkg/controller/serving/controller.go new file mode 100644 index 00000000..24bcc503 --- /dev/null +++ b/pkg/controller/serving/controller.go @@ -0,0 +1,18 @@ +package serving + +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/serving/index.go b/pkg/controller/serving/index.go new file mode 100644 index 00000000..4d25c099 --- /dev/null +++ b/pkg/controller/serving/index.go @@ -0,0 +1,57 @@ +package serving + +import ( + //"encoding/json" + //"flag" + // "fmt" + "io/ioutil" + //"log" + "path/filepath" + + "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/serving/register.go b/pkg/controller/serving/register.go new file mode 100644 index 00000000..81ad5b97 --- /dev/null +++ b/pkg/controller/serving/register.go @@ -0,0 +1,6 @@ +package serving + +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/serving/stack_controller.go b/pkg/controller/serving/serving_controller.go similarity index 65% rename from pkg/serving/stack_controller.go rename to pkg/controller/serving/serving_controller.go index e2daa8c5..33c3cd77 100644 --- a/pkg/serving/stack_controller.go +++ b/pkg/controller/serving/serving_controller.go @@ -1,4 +1,4 @@ -package stack +package serving import ( "context" @@ -20,7 +20,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/source" - d2index "github.com/elsony/devfile2-registry/tools/cmd/index" + // d2index "github.com/elsony/devfile2-registry/tools/cmd/index" + "encoding/json" ) var log = logf.Log.WithName("controller_stack") @@ -89,55 +90,77 @@ func (r *ReconcileStack) Reconcile(request reconcile.Request) (reconcile.Result, return reconcile.Result{}, err } - // Copy the yaml from the Stack to local devfile registry - for _, version := range instance.Spec.Versions { + 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) - basePath := "/devfiles" - devfile := fmt.Sprintf("%v/eclipse/%v/%v/devfile.yaml", basePath, instance.ObjectMeta.Name, version.Version) - metafile := fmt.Sprintf("%v/eclipse/%v/%v/meta.yaml", basePath, instance.ObjectMeta.Name, version.Version) - - 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.Devfile) - if err != nil { - reqLogger.Error(err, fmt.Sprintf("Error writing metafile %v", metafile)) + 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.Devfile) + if err != nil { + reqLogger.Error(err, fmt.Sprintf("Error writing metafile %v", metafile)) + } + f.Sync() + } } - f.Sync() - } // Regenerate the index indexfile := fmt.Sprintf("%v/index.json", basePath) - index, err := d2index.genIndex(basePath) + index, err := genIndex(basePath) if err != nil { reqLogger.Error(err, "Error generating devfile index") + return reconcile.Result{}, err } - b, err := json.MarshalIndent(index, "", " ") - if err != nil { - reqLogger.Error(err, "Error during marshal index") + 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) + + 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", metafile)) + reqLogger.Error(err, fmt.Sprintf("Error writing indexfile %v", indexfile)) + return reconcile.Result{}, err } f.Sync() diff --git a/pkg/serving/index.go b/pkg/serving/index.go deleted file mode 100644 index 49742faf..00000000 --- a/pkg/serving/index.go +++ /dev/null @@ -1,80 +0,0 @@ -package main - -import ( - "encoding/json" - "flag" - "fmt" - "io/ioutil" - "log" - "path/filepath" - - "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 - - dirs, err := ioutil.ReadDir(dir) - if err != nil { - return nil, err - } - - for _, file := range dirs { - if file.IsDir() { - var meta types.Meta - metaFile, err := ioutil.ReadFile(filepath.Join(dir, file.Name(), "meta.yaml")) - if err != nil { - return nil, err - } - err = yaml.Unmarshal(metaFile, &meta) - if err != nil { - return nil, err - } - - self := fmt.Sprintf("/%s/%s/%s", filepath.Base(dir), file.Name(), "devfile.yaml") - - metaIndex := types.MetaIndex{ - Meta: meta, - Links: types.Links{ - Self: self, - }, - } - index = append(index, metaIndex) - } - } - return index, nil -} - -func main() { - devfiles := flag.String("devfiles-dir", "", "Directory containing devfiles.") - output := flag.String("index", "", "Index filaname. This is where the index in JSON format will be saved.") - - flag.Parse() - - if *devfiles == "" { - log.Fatal("Provide devfile directory.") - } - - if *output == "" { - log.Fatal("Provide index file.") - } - - index, err := genIndex(*devfiles) - if err != nil { - log.Fatal(err) - } - b, err := json.MarshalIndent(index, "", " ") - if err != nil { - log.Fatal(err) - - } - err = ioutil.WriteFile(*output, b, 0644) - if err != nil { - log.Fatal(err) - - } -} From a950e37c89d3cb7a2a104634e2647034aa59911d Mon Sep 17 00:00:00 2001 From: Daniel Cleyrat Date: Fri, 5 Jun 2020 09:44:45 -0400 Subject: [PATCH 5/7] defile2 --- pkg/controller/serving/index.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/controller/serving/index.go b/pkg/controller/serving/index.go index 4d25c099..9e32c725 100644 --- a/pkg/controller/serving/index.go +++ b/pkg/controller/serving/index.go @@ -5,7 +5,7 @@ import ( //"flag" // "fmt" "io/ioutil" - //"log" + // "log" "path/filepath" "github.com/elsony/devfile2-registry/tools/types" @@ -15,6 +15,7 @@ import ( // 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) { + reqLogger := log.WithValues("Request.Namespace", "", "Request.Name", "") var index []types.MetaIndex @@ -32,6 +33,8 @@ func genIndex(dir string) ([]types.MetaIndex, error) { for _, versiondir := range versiondirs { var meta types.Meta metaFile, err := ioutil.ReadFile(filepath.Join(dir, stackdir.Name(), versiondir.Name(), "meta.yaml")) + reqLogger.Info(filepath.Join(dir, stackdir.Name(), versiondir.Name(), "meta.yaml")) + reqLogger.Info(string(metaFile)) if err != nil { return nil, err } @@ -39,6 +42,7 @@ func genIndex(dir string) ([]types.MetaIndex, error) { if err != nil { return nil, err } + reqLogger.Info(fmt.Sprintf("%v", meta) self := filepath.Join(stackdir.Name(), versiondir.Name(), "devfile.yaml") @@ -48,6 +52,7 @@ func genIndex(dir string) ([]types.MetaIndex, error) { Self: self, }, } + reqLogger.Info(fmt.Sprintf("%v", metaIndex) index = append(index, metaIndex) } } From a23b6f8c9068d70f97dfb912f5fabbf908e3661e Mon Sep 17 00:00:00 2001 From: Daniel Cleyrat Date: Fri, 5 Jun 2020 16:20:18 -0400 Subject: [PATCH 6/7] deffile2 --- pkg/controller/serving/index.go | 11 ++--------- pkg/controller/serving/serving_controller.go | 2 +- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/pkg/controller/serving/index.go b/pkg/controller/serving/index.go index 9e32c725..9733544b 100644 --- a/pkg/controller/serving/index.go +++ b/pkg/controller/serving/index.go @@ -3,9 +3,9 @@ package serving import ( //"encoding/json" //"flag" - // "fmt" + //"fmt" "io/ioutil" - // "log" + //"log" "path/filepath" "github.com/elsony/devfile2-registry/tools/types" @@ -15,7 +15,6 @@ import ( // 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) { - reqLogger := log.WithValues("Request.Namespace", "", "Request.Name", "") var index []types.MetaIndex @@ -33,8 +32,6 @@ func genIndex(dir string) ([]types.MetaIndex, error) { for _, versiondir := range versiondirs { var meta types.Meta metaFile, err := ioutil.ReadFile(filepath.Join(dir, stackdir.Name(), versiondir.Name(), "meta.yaml")) - reqLogger.Info(filepath.Join(dir, stackdir.Name(), versiondir.Name(), "meta.yaml")) - reqLogger.Info(string(metaFile)) if err != nil { return nil, err } @@ -42,17 +39,13 @@ func genIndex(dir string) ([]types.MetaIndex, error) { if err != nil { return nil, err } - reqLogger.Info(fmt.Sprintf("%v", meta) - self := filepath.Join(stackdir.Name(), versiondir.Name(), "devfile.yaml") - metaIndex := types.MetaIndex{ Meta: meta, Links: types.Links{ Self: self, }, } - reqLogger.Info(fmt.Sprintf("%v", metaIndex) index = append(index, metaIndex) } } diff --git a/pkg/controller/serving/serving_controller.go b/pkg/controller/serving/serving_controller.go index 33c3cd77..43930ba7 100644 --- a/pkg/controller/serving/serving_controller.go +++ b/pkg/controller/serving/serving_controller.go @@ -125,7 +125,7 @@ func (r *ReconcileStack) Reconcile(request reconcile.Request) (reconcile.Result, reqLogger.Error(err, fmt.Sprintf("Error creating metafile %v", metafile)) } defer f.Close() - _, err = f.WriteString(version.Devfile) + _, err = f.WriteString(version.Metafile) if err != nil { reqLogger.Error(err, fmt.Sprintf("Error writing metafile %v", metafile)) } From 3aadbb4f768871124b8b91ac616c7bc6a42552b8 Mon Sep 17 00:00:00 2001 From: kaczyns Date: Tue, 16 Jun 2020 16:24:47 -0400 Subject: [PATCH 7/7] rename serving->devfile-registry, CRD list/map changes --- Makefile | 6 +- build/Dockerfile | 2 +- .../main.go | 464 +++++++++--------- .../0.1/devfile-registry-controller.yaml} | 54 +- config/samples/full.yaml | 4 +- config/versions.yaml | 8 +- deploy/crds/kabanero.io_kabaneros_crd.yaml | 62 ++- deploy/crds/kabanero.io_stacks_crd.yaml | 36 +- pkg/apis/kabanero/v1alpha1/kabanero_types.go | 3 +- pkg/apis/kabanero/v1alpha2/kabanero_types.go | 33 +- pkg/apis/kabanero/v1alpha2/stack_types.go | 22 +- .../v1alpha2/zz_generated.deepcopy.go | 34 +- .../controller.go | 2 +- .../devfile_registry_controller.go} | 338 ++++++------- .../{serving => devfileregistry}/index.go | 2 +- .../{serving => devfileregistry}/register.go | 2 +- .../{serving.go => devfile-registry.go} | 279 ++++++----- .../kabaneroplatform_controller.go | 6 +- 18 files changed, 727 insertions(+), 630 deletions(-) rename cmd/{serving => devfile-registry-controller}/main.go (95%) rename config/orchestrations/{serving/0.1/serving.yaml => devfile-registry-controller/0.1/devfile-registry-controller.yaml} (66%) rename pkg/controller/{serving => devfileregistry}/controller.go (94%) rename pkg/controller/{serving/serving_controller.go => devfileregistry/devfile_registry_controller.go} (96%) rename pkg/controller/{serving => devfileregistry}/index.go (98%) rename pkg/controller/{serving => devfileregistry}/register.go (87%) rename pkg/controller/kabaneroplatform/{serving.go => devfile-registry.go} (55%) diff --git a/Makefile b/Makefile index 52fbc5fe..156430fe 100755 --- a/Makefile +++ b/Makefile @@ -72,7 +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/serving + 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 @@ -82,7 +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/serving -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/serving + 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) . @@ -197,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 build/_output/bin/serving | 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 ada9dd91..66d9f329 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -17,7 +17,7 @@ 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/serving /usr/local/bin/serving +COPY build/_output/bin/devfile-registry-controller /usr/local/bin/devfile-registry-controller RUN mkdir /devfiles && chmod +777 /devfiles diff --git a/cmd/serving/main.go b/cmd/devfile-registry-controller/main.go similarity index 95% rename from cmd/serving/main.go rename to cmd/devfile-registry-controller/main.go index e7cdffb0..86b4086d 100644 --- a/cmd/serving/main.go +++ b/cmd/devfile-registry-controller/main.go @@ -1,232 +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/serving" - - "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, "serving-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 := serving.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 -} +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/serving/0.1/serving.yaml b/config/orchestrations/devfile-registry-controller/0.1/devfile-registry-controller.yaml similarity index 66% rename from config/orchestrations/serving/0.1/serving.yaml rename to config/orchestrations/devfile-registry-controller/0.1/devfile-registry-controller.yaml index ddba8738..31bb0ad6 100644 --- a/config/orchestrations/serving/0.1/serving.yaml +++ b/config/orchestrations/devfile-registry-controller/0.1/devfile-registry-controller.yaml @@ -1,19 +1,19 @@ apiVersion: v1 kind: Service metadata: - name: kabanero-operator-serving + name: kabanero-operator-devfile-registry annotations: - service.beta.openshift.io/serving-cert-secret-name: kabanero-operator-serving-cert + service.beta.openshift.io/serving-cert-secret-name: kabanero-operator-devfile-registry-cert labels: - app.kubernetes.io/name: kabanero-operator-serving + app.kubernetes.io/name: kabanero-operator-devfile-registry app.kubernetes.io/instance: {{ .instance }} app.kubernetes.io/version: {{ .version }} - app.kubernetes.io/component: serving + app.kubernetes.io/component: devfile-registry app.kubernetes.io/part-of: kabanero app.kubernetes.io/managed-by: kabanero-operator spec: selector: - name: kabanero-operator-serving + name: kabanero-operator-devfile-registry ports: - protocol: TCP port: 443 @@ -23,7 +23,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: creationTimestamp: null - name: kabanero-operator-serving + name: kabanero-operator-devfile-registry rules: - apiGroups: - "" @@ -91,28 +91,28 @@ rules: apiVersion: v1 kind: ServiceAccount metadata: - name: kabanero-operator-serving + name: kabanero-operator-devfile-registry --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: - name: kabanero-operator-serving + name: kabanero-operator-devfile-registry subjects: - kind: ServiceAccount - name: kabanero-operator-serving + name: kabanero-operator-devfile-registry roleRef: kind: Role - name: kabanero-operator-serving + name: kabanero-operator-devfile-registry apiGroup: rbac.authorization.k8s.io --- apiVersion: route.openshift.io/v1 kind: Route metadata: - name: kabanero-operator-serving + name: kabanero-operator-devfile-registry spec: to: kind: Service - name: kabanero-operator-serving + name: kabanero-operator-devfile-registry tls: termination: reencrypt insecureEdgeTerminationPolicy: Redirect @@ -120,38 +120,38 @@ spec: apiVersion: apps/v1 kind: Deployment metadata: - name: kabanero-operator-serving + name: kabanero-operator-devfile-registry labels: - name: kabanero-operator-serving - app.kubernetes.io/name: kabanero-operator-serving + 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: serving + 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-serving + name: kabanero-operator-devfile-registry template: metadata: labels: - name: kabanero-operator-serving - app.kubernetes.io/name: kabanero-operator-serving + 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: serving + app.kubernetes.io/component: devfile-registry app.kubernetes.io/part-of: kabanero app.kubernetes.io/managed-by: kabanero-operator spec: - serviceAccount: kabanero-operator-serving + serviceAccount: kabanero-operator-devfile-registry containers: - - name: kabanero-operator-serving + - name: kabanero-operator-devfile-registry image: {{ .image }} imagePullPolicy: Always command: - - /usr/local/bin/serving + - /usr/local/bin/devfile-registry-controller env: - name: WATCH_NAMESPACE valueFrom: @@ -162,12 +162,12 @@ spec: fieldRef: fieldPath: metadata.name - name: OPERATOR_NAME - value: "kabanero-operator-serving" + value: "kabanero-operator-devfile-registry" volumeMounts: - mountPath: /tmp/serving-certs - name: kabanero-operator-serving-cert + name: kabanero-operator-devfile-registry-cert readOnly: true volumes: - - name: kabanero-operator-serving-cert + - name: kabanero-operator-devfile-registry-cert secret: - secretName: kabanero-operator-serving-cert + secretName: kabanero-operator-devfile-registry-cert diff --git a/config/samples/full.yaml b/config/samples/full.yaml index 3ecf9110..6fd9298f 100644 --- a/config/samples/full.yaml +++ b/config/samples/full.yaml @@ -67,9 +67,9 @@ spec: # Overrides the image uri image: kabanero/kabanero-operator:TRAVIS_TAG - serving: + devfileRegistry: # Overrides the setting for version on this component - version: "0.9.0" + version: "0.10.0" # Overrides the image as a separate repository or tag repository: kabanero/kabanero-operator diff --git a/config/versions.yaml b/config/versions.yaml index c46f0b88..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: @@ -44,7 +45,6 @@ kabanero: admission-webhook: "0.9.0" sso: "7.3.2" codeready-workspaces: "0.9.0" - serving: "0.9.0" - version: "0.8.0" related-versions: @@ -304,9 +304,9 @@ related-software: - version: "7.3.2" orchestrations: "orchestrations/sso/0.1" - serving: - - version: "0.9.0" - orchestrations: "orchestrations/serving/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 9c5796e5..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 @@ -558,17 +574,6 @@ spec: version: type: string type: object - serving: - properties: - image: - type: string - repository: - type: string - tag: - type: string - version: - type: string - type: object sso: properties: adminSecretName: @@ -632,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 @@ -703,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 @@ -752,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 @@ -883,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: @@ -908,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 867df3e8..8b2dd02b 100644 --- a/deploy/crds/kabanero.io_stacks_crd.yaml +++ b/deploy/crds/kabanero.io_stacks_crd.yaml @@ -63,7 +63,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 metafile: type: string pipelines: @@ -103,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: @@ -112,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 @@ -146,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: @@ -177,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: @@ -202,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: @@ -211,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/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 c0fb11fc..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,7 +47,7 @@ type KabaneroSpec struct { AdmissionControllerWebhook AdmissionControllerWebhookCustomizationSpec `json:"admissionControllerWebhook,omitempty"` - Serving ServingSpec `json:"serving,omitempty"` + DevfileRegistry DevfileRegistrySpec `json:"devfileRegistry,omitempty"` Sso SsoCustomizationSpec `json:"sso,omitempty"` @@ -53,7 +55,9 @@ type KabaneroSpec struct { } type GitopsSpec struct { - // +listType=set + // +listType=map + // +listMapKey=id + // +listMapKey=sha256 Pipelines []PipelineSpec `json:"pipelines,omitempty"` } @@ -73,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"` } @@ -121,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"` @@ -234,7 +243,7 @@ type AdmissionControllerWebhookCustomizationSpec struct { Tag string `json:"tag,omitempty"` } -type ServingSpec struct { +type DevfileRegistrySpec struct { Version string `json:"version,omitempty"` Image string `json:"image,omitempty"` Repository string `json:"repository,omitempty"` @@ -311,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 7b84edd5..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,12 +56,16 @@ 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 + // +listType=map + // +listMapKey=id + // +listMapKey=image Images []Image `json:"images,omitempty"` Devfile string `json:"devfile,omitempty"` Metafile string `json:"metafile,omitempty"` @@ -112,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"` } @@ -129,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 e0f059d0..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,7 +663,7 @@ func (in *KabaneroSpec) DeepCopyInto(out *KabaneroSpec) { out.CollectionController = in.CollectionController out.StackController = in.StackController out.AdmissionControllerWebhook = in.AdmissionControllerWebhook - out.Serving = in.Serving + out.DevfileRegistry = in.DevfileRegistry out.Sso = in.Sso in.Gitops.DeepCopyInto(&out.Gitops) return @@ -848,22 +864,6 @@ func (in *ServerlessStatus) DeepCopy() *ServerlessStatus { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ServingSpec) DeepCopyInto(out *ServingSpec) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServingSpec. -func (in *ServingSpec) DeepCopy() *ServingSpec { - if in == nil { - return nil - } - out := new(ServingSpec) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SsoCustomizationSpec) DeepCopyInto(out *SsoCustomizationSpec) { *out = *in diff --git a/pkg/controller/serving/controller.go b/pkg/controller/devfileregistry/controller.go similarity index 94% rename from pkg/controller/serving/controller.go rename to pkg/controller/devfileregistry/controller.go index 24bcc503..82bda823 100644 --- a/pkg/controller/serving/controller.go +++ b/pkg/controller/devfileregistry/controller.go @@ -1,4 +1,4 @@ -package serving +package devfileregistry import ( "sigs.k8s.io/controller-runtime/pkg/manager" diff --git a/pkg/controller/serving/serving_controller.go b/pkg/controller/devfileregistry/devfile_registry_controller.go similarity index 96% rename from pkg/controller/serving/serving_controller.go rename to pkg/controller/devfileregistry/devfile_registry_controller.go index 7247e30d..9253f696 100644 --- a/pkg/controller/serving/serving_controller.go +++ b/pkg/controller/devfileregistry/devfile_registry_controller.go @@ -1,169 +1,169 @@ -package serving - -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 -} +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/serving/index.go b/pkg/controller/devfileregistry/index.go similarity index 98% rename from pkg/controller/serving/index.go rename to pkg/controller/devfileregistry/index.go index c92d4c81..8d9ef3b0 100644 --- a/pkg/controller/serving/index.go +++ b/pkg/controller/devfileregistry/index.go @@ -1,4 +1,4 @@ -package serving +package devfileregistry import ( //"encoding/json" diff --git a/pkg/controller/serving/register.go b/pkg/controller/devfileregistry/register.go similarity index 87% rename from pkg/controller/serving/register.go rename to pkg/controller/devfileregistry/register.go index 81ad5b97..8c875069 100644 --- a/pkg/controller/serving/register.go +++ b/pkg/controller/devfileregistry/register.go @@ -1,4 +1,4 @@ -package serving +package devfileregistry func init() { // AddToManagerFuncs is a list of functions to create controllers and add them to a manager. diff --git a/pkg/controller/kabaneroplatform/serving.go b/pkg/controller/kabaneroplatform/devfile-registry.go similarity index 55% rename from pkg/controller/kabaneroplatform/serving.go rename to pkg/controller/kabaneroplatform/devfile-registry.go index d560bcba..c01e6b0d 100644 --- a/pkg/controller/kabaneroplatform/serving.go +++ b/pkg/controller/kabaneroplatform/devfile-registry.go @@ -1,129 +1,150 @@ -package kabaneroplatform - -import ( - "context" - "github.com/go-logr/logr" - kabanerov1alpha2 "github.com/kabanero-io/kabanero-operator/pkg/apis/kabanero/v1alpha2" - - 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 reconcileServing(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. - rev, err := resolveSoftwareRevision(k, "serving", k.Spec.Serving.Version) - if err != nil { - return err - } - - templateContext := rev.Identifiers - - image, err := imageUriWithOverrides(k.Spec.Serving.Repository, k.Spec.Serving.Tag, k.Spec.Serving.Image, rev) - if err != nil { - return err - } - - templateContext["image"] = image - templateContext["instance"] = k.ObjectMeta.UID - templateContext["version"] = rev.Version - - f, err := rev.OpenOrchestration("serving.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 cleanupServing(k *kabanerov1alpha2.Kabanero, c client.Client, reqLogger logr.Logger) error { - - rev, err := resolveSoftwareRevision(k, "serving", k.Spec.Serving.Version) - if err != nil { - return err - } - - //The context which will be used to render any templates - templateContext := rev.Identifiers - - // TODO - image, err := imageUriWithOverrides(k.Spec.Serving.Repository, k.Spec.Serving.Tag, k.Spec.Serving.Image, rev) - if err != nil { - return err - } - templateContext["image"] = image - templateContext["instance"] = k.ObjectMeta.UID - templateContext["version"] = rev.Version - - f, err := rev.OpenOrchestration("serving.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-serving-cert" - secretInstance.Namespace = k.GetNamespace() - err = c.Delete(context.TODO(), secretInstance) - - if (err != nil) && (errors.IsNotFound(err) == false) { - return err - } - - return nil -} +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 f7f3eae8..a17bdd64 100644 --- a/pkg/controller/kabaneroplatform/kabaneroplatform_controller.go +++ b/pkg/controller/kabaneroplatform/kabaneroplatform_controller.go @@ -56,7 +56,7 @@ var reconcileFuncs = []reconcileFuncType{ {name: "sso", function: reconcileSso}, {name: "gitops", function: reconcileGitopsPipelines}, {name: "target namespaces", function: reconcileTargetNamespaces}, - {name: "serving", function: reconcileServing}, + {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 @@ -503,8 +503,8 @@ func cleanup(ctx context.Context, k *kabanerov1alpha2.Kabanero, client client.Cl return err } - // Cleanup the Serving and their cross-namespace objects - err = cleanupServing(k, client, reqLogger) + // Cleanup the Devfile registry controller and its cross-namespace objects + err = cleanupDevfileRegistry(k, client, reqLogger) if err != nil { return err }