From e83ffc4a5881aac38fea711f3f46f68b4f63e9d1 Mon Sep 17 00:00:00 2001 From: John Collier Date: Fri, 13 Nov 2020 12:24:14 -0500 Subject: [PATCH 1/9] Implement the DevfileRegistry custom resource Implements the DevfileRegistry custom resource based on the devfile registry operator design doc. In addition to the spec, `dr` and `devreg` are added as shortnames. Signed-off-by: John Collier --- api/v1alpha1/devfileregistry_types.go | 47 +++++++++++++- api/v1alpha1/zz_generated.deepcopy.go | 60 ++++++++++++++++- ...registry.devfile.io_devfileregistries.yaml | 64 ++++++++++++++++++- 3 files changed, 165 insertions(+), 6 deletions(-) diff --git a/api/v1alpha1/devfileregistry_types.go b/api/v1alpha1/devfileregistry_types.go index 05a4b3e..4dec44f 100644 --- a/api/v1alpha1/devfileregistry_types.go +++ b/api/v1alpha1/devfileregistry_types.go @@ -23,20 +23,63 @@ type DevfileRegistrySpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "make" to regenerate code after modifying this file - // Foo is an example field of DevfileRegistry. Edit DevfileRegistry_types.go to remove/update - Foo string `json:"foo,omitempty"` + // Sets the container image containing devfile stacks to be deployed on the Devfile Registry + DevfileIndexImage string `json:"devfileIndexImage,omitempty"` + + // Overrides the container image used for the OCI registry. + // Recommended to leave blank and default to the image specified by the operator. + // +optional + OciRegistryImage string `json:"ociRegistryImage,omitempty"` + Storage DevfileRegistrySpecStorage `json:"storage,omitempty"` + TLS DevfileRegistrySpecTLS `json:"tls,omitempty"` + K8s DevfileRegistrySpecK8sOnly `json:"k8s,omitempty"` +} + +// DevfileRegistrySpecStorage defines the desired state of the storage for the DevfileRegistry +type DevfileRegistrySpecStorage struct { + // Instructs the operator to deploy the DevfileRegistry with persistent storage + // Enabled by default. Disabling is only recommended for development or test. + // +optional + Enabled *bool `json:"enabled,omitempty"` + + // Configures the size of the devfile registry's persistent volume, if enabled. + // Defaults to 1Gi. + // +optional + RegistryVolumeSize string `json:"ociRegistryImage,omitempty"` +} + +// DevfileRegistrySpecTLS defines the desired state for TLS in the DevfileRegistry +type DevfileRegistrySpecTLS struct { + // Instructs the operator to deploy the DevfileRegistry with TLS enabled. + // Enabled by default. Disabling is only recommended for development or test. + // +optional + Enabled *bool `json:"enabled,omitempty"` + + // Name of an optional, pre-existing TLS secret to use for TLS termination on ingress/route resources. + // +optional + SecretName string `json:"ociRegistryImage,omitempty"` +} + +// DevfileRegistrySpecK8sOnly defines the desired state of the kubernetes-only fields of the DevfileRegistry +type DevfileRegistrySpecK8sOnly struct { + // Ingress domain for a Kubernetes cluster. This MUST be explicitly specified on Kubernetes. There are no defaults + IngressDomain string `json:"ingressDomain,omitempty"` } // DevfileRegistryStatus defines the observed state of DevfileRegistry type DevfileRegistryStatus struct { // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster // Important: Run "make" to regenerate code after modifying this file + URL string `json:"url"` } // +kubebuilder:object:root=true // +kubebuilder:subresource:status // DevfileRegistry is the Schema for the devfileregistries API +// +kubebuilder:resource:path=devfileregistries,shortName=devreg;dr +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="URL",type="string",JSONPath=".status.url",description="The URL for the Devfile Registry" type DevfileRegistry struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 47719cb..8380e53 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -24,7 +24,7 @@ func (in *DevfileRegistry) DeepCopyInto(out *DevfileRegistry) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) out.Status = in.Status } @@ -81,6 +81,9 @@ func (in *DevfileRegistryList) DeepCopyObject() runtime.Object { // 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 + in.Storage.DeepCopyInto(&out.Storage) + in.TLS.DeepCopyInto(&out.TLS) + out.K8s = in.K8s } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DevfileRegistrySpec. @@ -93,6 +96,61 @@ func (in *DevfileRegistrySpec) DeepCopy() *DevfileRegistrySpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DevfileRegistrySpecK8sOnly) DeepCopyInto(out *DevfileRegistrySpecK8sOnly) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DevfileRegistrySpecK8sOnly. +func (in *DevfileRegistrySpecK8sOnly) DeepCopy() *DevfileRegistrySpecK8sOnly { + if in == nil { + return nil + } + out := new(DevfileRegistrySpecK8sOnly) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DevfileRegistrySpecStorage) DeepCopyInto(out *DevfileRegistrySpecStorage) { + *out = *in + if in.Enabled != nil { + in, out := &in.Enabled, &out.Enabled + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DevfileRegistrySpecStorage. +func (in *DevfileRegistrySpecStorage) DeepCopy() *DevfileRegistrySpecStorage { + if in == nil { + return nil + } + out := new(DevfileRegistrySpecStorage) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DevfileRegistrySpecTLS) DeepCopyInto(out *DevfileRegistrySpecTLS) { + *out = *in + if in.Enabled != nil { + in, out := &in.Enabled, &out.Enabled + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DevfileRegistrySpecTLS. +func (in *DevfileRegistrySpecTLS) DeepCopy() *DevfileRegistrySpecTLS { + if in == nil { + return nil + } + out := new(DevfileRegistrySpecTLS) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DevfileRegistryStatus) DeepCopyInto(out *DevfileRegistryStatus) { *out = *in diff --git a/config/crd/bases/registry.devfile.io_devfileregistries.yaml b/config/crd/bases/registry.devfile.io_devfileregistries.yaml index 3e53c87..6ee3404 100644 --- a/config/crd/bases/registry.devfile.io_devfileregistries.yaml +++ b/config/crd/bases/registry.devfile.io_devfileregistries.yaml @@ -8,11 +8,19 @@ metadata: creationTimestamp: null name: devfileregistries.registry.devfile.io spec: + additionalPrinterColumns: + - JSONPath: .status.url + description: The URL for the Devfile Registry + name: URL + type: string group: registry.devfile.io names: kind: DevfileRegistry listKind: DevfileRegistryList plural: devfileregistries + shortNames: + - devreg + - dr singular: devfileregistry scope: Namespaced subresources: @@ -36,13 +44,63 @@ spec: spec: description: DevfileRegistrySpec defines the desired state of DevfileRegistry properties: - foo: - description: Foo is an example field of DevfileRegistry. Edit DevfileRegistry_types.go - to remove/update + devfileIndexImage: + description: Sets the container image containing devfile stacks to be + deployed on the Devfile Registry type: string + k8s: + description: DevfileRegistrySpecK8sOnly defines the desired state of + the kubernetes-only fields of the DevfileRegistry + properties: + ingressDomain: + description: Ingress domain for a Kubernetes cluster. This MUST + be explicitly specified on Kubernetes. There are no defaults + type: string + type: object + ociRegistryImage: + description: Overrides the container image used for the OCI registry. + Recommended to leave blank and default to the image specified by the + operator. + type: string + storage: + description: DevfileRegistrySpecStorage defines the desired state of + the storage for the DevfileRegistry + properties: + enabled: + description: Instructs the operator to deploy the DevfileRegistry + with persistent storage Enabled by default. Disabling is only + recommended for development or test. + type: boolean + ociRegistryImage: + description: Configures the size of the devfile registry's persistent + volume, if enabled. Defaults to 1Gi. + type: string + type: object + tls: + description: DevfileRegistrySpecTLS defines the desired state for TLS + in the DevfileRegistry + properties: + enabled: + description: Instructs the operator to deploy the DevfileRegistry + with TLS enabled. Enabled by default. Disabling is only recommended + for development or test. + type: boolean + ociRegistryImage: + description: Name of an optional, pre-existing TLS secret to use + for TLS termination on ingress/route resources. + type: string + type: object type: object status: description: DevfileRegistryStatus defines the observed state of DevfileRegistry + properties: + url: + description: 'INSERT ADDITIONAL STATUS FIELD - define observed state + of cluster Important: Run "make" to regenerate code after modifying + this file' + type: string + required: + - url type: object type: object version: v1alpha1 From 82641d0683e914c37ef6c4831fc4154c3667c357 Mon Sep 17 00:00:00 2001 From: John Collier Date: Fri, 13 Nov 2020 13:07:33 -0500 Subject: [PATCH 2/9] Implement the Devfile Registry operator core logic Implements the core business logic of the Devfile Registry operator, allowing registries to be deployed on Kubernetes and OpenShift. Signed-off-by: John Collier --- .github/workflows/dockerimage-push.yaml | 31 +++ Dockerfile | 1 + Makefile | 12 +- README.md | 60 ++++- config/manager/kustomization.yaml | 6 + config/manager/manager.yaml | 6 +- config/rbac/role.yaml | 58 +++++ .../registry_v1alpha1_devfileregistry.yaml | 4 +- controllers/devfileregistry_controller.go | 155 +++++++++++- controllers/ensure.go | 181 ++++++++++++++ controllers/update.go | 170 +++++++++++++ go.mod | 4 + go.sum | 45 ++++ main.go | 4 +- pkg/cluster/info.go | 51 ++++ pkg/config/config.go | 27 +++ pkg/registry/defaults.go | 80 +++++++ pkg/registry/defaults_test.go | 225 ++++++++++++++++++ pkg/registry/deployment.go | 116 +++++++++ pkg/registry/ingress.go | 70 ++++++ pkg/registry/naming.go | 46 ++++ pkg/registry/route.go | 82 +++++++ pkg/registry/service.go | 44 ++++ pkg/registry/util.go | 28 +++ pkg/registry/volume.go | 40 ++++ pkg/util/util.go | 40 ++++ 26 files changed, 1571 insertions(+), 15 deletions(-) create mode 100644 .github/workflows/dockerimage-push.yaml create mode 100644 controllers/ensure.go create mode 100644 controllers/update.go create mode 100644 pkg/cluster/info.go create mode 100644 pkg/config/config.go create mode 100644 pkg/registry/defaults.go create mode 100644 pkg/registry/defaults_test.go create mode 100644 pkg/registry/deployment.go create mode 100644 pkg/registry/ingress.go create mode 100644 pkg/registry/naming.go create mode 100644 pkg/registry/route.go create mode 100644 pkg/registry/service.go create mode 100644 pkg/registry/util.go create mode 100644 pkg/registry/volume.go create mode 100644 pkg/util/util.go diff --git a/.github/workflows/dockerimage-push.yaml b/.github/workflows/dockerimage-push.yaml new file mode 100644 index 0000000..47b6e04 --- /dev/null +++ b/.github/workflows/dockerimage-push.yaml @@ -0,0 +1,31 @@ +# +# Copyright (c) 2020 Red Hat, Inc. +# This program and the accompanying materials are made +# available under the terms of the Eclipse Public License 2.0 +# which is available at https://www.eclipse.org/legal/epl-2.0/ +# +# SPDX-License-Identifier: EPL-2.0 +# +name: Next Dockerimage + +on: + push: + branches: [ main ] + +jobs: + + build: + runs-on: ubuntu-latest + steps: + - name: Checkout registry-operator source code + uses: actions/checkout@v2 + - name: Docker Build & Push + uses: docker/build-push-action@v1.1.0 + with: + username: ${{ secrets.QUAY_USERNAME }} + password: ${{ secrets.QUAY_PASSWORD }} + registry: quay.io + repository: devfile/registry-operator + dockerfile: Dockerfile + tags: next + tag_with_sha: true diff --git a/Dockerfile b/Dockerfile index 74eb9d7..c1b10a4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,6 +13,7 @@ RUN go mod download COPY main.go main.go COPY api/ api/ COPY controllers/ controllers/ +COPY pkg/ pkg/ # Build RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -a -o manager main.go diff --git a/Makefile b/Makefile index 57279e0..5b3a6da 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,12 @@ ENVTEST_ASSETS_DIR = $(shell pwd)/testbin test: generate fmt vet manifests mkdir -p $(ENVTEST_ASSETS_DIR) test -f $(ENVTEST_ASSETS_DIR)/setup-envtest.sh || curl -sSLo $(ENVTEST_ASSETS_DIR)/setup-envtest.sh https://raw.githubusercontent.com/kubernetes-sigs/controller-runtime/v0.6.3/hack/setup-envtest.sh - source $(ENVTEST_ASSETS_DIR)/setup-envtest.sh; fetch_envtest_tools $(ENVTEST_ASSETS_DIR); setup_envtest_env $(ENVTEST_ASSETS_DIR); go test ./... -coverprofile cover.out + source $(ENVTEST_ASSETS_DIR)/setup-envtest.sh; fetch_envtest_tools $(ENVTEST_ASSETS_DIR); setup_envtest_env $(ENVTEST_ASSETS_DIR); go test ./pkg/... -coverprofile cover.out + +### test-integration: runs integration tests on the cluster set in context. +test-integration: generate fmt vet manifests + CGO_ENABLED=0 go test -v -c -o bin/devfileregistry-operator-integration ./tests/integration/cmd/devfileregistry_test.go + ./bin/devfileregistry-operator-integration # Build manager binary manager: generate fmt vet @@ -45,9 +50,10 @@ run: generate fmt vet manifests install: manifests kustomize $(KUSTOMIZE) build config/crd | kubectl apply -f - -# Uninstall CRDs from a cluster +# Uninstall operator and CRDs from a cluster uninstall: manifests kustomize - $(KUSTOMIZE) build config/crd | kubectl delete -f - + $(KUSTOMIZE) build config/default | kubectl delete -f - + #$(KUSTOMIZE) build config/crd | kubectl delete -f - # Deploy controller in the configured Kubernetes cluster in ~/.kube/config deploy: manifests kustomize diff --git a/README.md b/README.md index d2eeace..65eff32 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,58 @@ -# registry-operator -Operator for devfile registry +# Devfile Registry Operator + +Devfile Registry operator repository that contains the operator for the DevfileRegistry Custom Resource. + +## Issue Tracking + +Issue tracking repo: https://github.com/devfile/api with label area/registry + +## Running the controller in a cluster + +The controller can be deployed to a cluster provided you are logged in with cluster-admin credentials: + +```bash +export IMG=johncollier/registry-operator:v0.0.1 +make install && make deploy +``` + +## Development + +The repository contains a Makefile; building and deploying can be configured via the environment variables + +|variable|purpose|default value| +|---|---|---| +| `IMG` | Image used for controller | `quay.io/devfile/registry-operator:next` | + +Some of the rules supported by the makefile: + +|rule|purpose| +|---|---| +| docker-build | build registry operator docker image | +| docker-push | push registry operator docker image | +| deploy | deploy operator to cluster | +| install | create the devfile registry CRDs on the cluster | +| uninstall | remove the devfile registry operator and CRDs from the cluster | +| manifests | Generate manifests e.g. CRD, RBAC etc. | +| generate | Generate the API type definitions. Must be run after modifying the DevfileRegistry type. | +| test_integration | Run the integration tests for the operator. | + +To see all rules supported by the makefile, run `make help` + +## Testing + +To run integration tests for the operator, run `make test_integration`. + +By default, the tests will use the default image for the operator, `quay.io/devfile/registry-operator:next`. To use your own image, run: + +``` +export IMG= +make test_integration +``` + +### Run operator locally +It's possible to run an instance of the operator locally while communicating with a cluster. + +```bash +export NAMESPACE=devfileregistry-operator +make run ENABLE_WEBHOOKS=false +``` \ No newline at end of file diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 5c5f0b8..6a5141f 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -1,2 +1,8 @@ resources: - manager.yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +images: +- name: controller + newName: quay.io/devfile/registry-operator + newTag: next diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index b6c85a5..012a808 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -12,22 +12,26 @@ metadata: namespace: system labels: control-plane: controller-manager + app.kubernetes.io/name: devfileregistry-operator spec: selector: matchLabels: control-plane: controller-manager + app.kubernetes.io/name: devfileregistry-operator replicas: 1 template: metadata: labels: control-plane: controller-manager + app.kubernetes.io/name: devfileregistry-operator spec: containers: - command: - /manager args: - --enable-leader-election - image: controller:latest + image: quay.io/devfile/registry-operator:next + imagePullPolicy: Always name: manager resources: limits: diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 5f3230b..f266715 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -6,6 +6,50 @@ metadata: creationTimestamp: null name: manager-role rules: +- apiGroups: + - apps + resources: + - deployments + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - "" + resources: + - persistentvolumeclaims + - services + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - "" + resources: + - pods + verbs: + - get + - list +- apiGroups: + - extensions + resources: + - ingresses + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - registry.devfile.io resources: @@ -21,8 +65,22 @@ rules: - apiGroups: - registry.devfile.io resources: + - devfileregistries/finalizers - devfileregistries/status verbs: - get - patch - update +- apiGroups: + - route.openshift.io + resources: + - routes + - routes/custom-host + verbs: + - create + - delete + - get + - list + - patch + - update + - watch diff --git a/config/samples/registry_v1alpha1_devfileregistry.yaml b/config/samples/registry_v1alpha1_devfileregistry.yaml index 9a0944b..414341e 100644 --- a/config/samples/registry_v1alpha1_devfileregistry.yaml +++ b/config/samples/registry_v1alpha1_devfileregistry.yaml @@ -3,5 +3,5 @@ kind: DevfileRegistry metadata: name: devfileregistry-sample spec: - # Add fields here - foo: bar + bootstrapImage: quay.io/devfile/metadata-server:latest + ociRegistryImage: registry:latest \ No newline at end of file diff --git a/controllers/devfileregistry_controller.go b/controllers/devfileregistry_controller.go index 439ff7a..658255f 100644 --- a/controllers/devfileregistry_controller.go +++ b/controllers/devfileregistry_controller.go @@ -13,13 +13,24 @@ package controllers import ( "context" + "time" "github.com/go-logr/logr" + routev1 "github.com/openshift/api/route/v1" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/api/extensions/v1beta1" + "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" registryv1alpha1 "github.com/devfile/registry-operator/api/v1alpha1" + "github.com/devfile/registry-operator/pkg/cluster" + "github.com/devfile/registry-operator/pkg/config" + "github.com/devfile/registry-operator/pkg/registry" + "github.com/devfile/registry-operator/pkg/util" ) // DevfileRegistryReconciler reconciles a DevfileRegistry object @@ -30,19 +41,151 @@ type DevfileRegistryReconciler struct { } // +kubebuilder:rbac:groups=registry.devfile.io,resources=devfileregistries,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=registry.devfile.io,resources=devfileregistries/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=registry.devfile.io,resources=devfileregistries/status;devfileregistries/finalizers,verbs=get;update;patch +// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=core,resources=services;persistentvolumeclaims,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=core,resources=pods,verbs=get;list; +// +kubebuilder:rbac:groups=extensions,resources=ingresses,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=route.openshift.io,resources=routes;routes/custom-host,verbs=get;list;watch;create;update;patch;delete func (r *DevfileRegistryReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { - _ = context.Background() - _ = r.Log.WithValues("devfileregistry", req.NamespacedName) + ctx := context.Background() + log := r.Log.WithValues("devfileregistry", req.NamespacedName) - // your logic here + // Fetch the DevfileRegistry instance + devfileRegistry := ®istryv1alpha1.DevfileRegistry{} + err := r.Get(ctx, req.NamespacedName, devfileRegistry) + 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 + log.Info("DevfileRegistry resource not found. Ignoring since object must be deleted") + return ctrl.Result{}, nil + } + // Error reading the object - requeue the request. + log.Error(err, "Failed to get DevfileRegistry") + return ctrl.Result{}, err + } + + // Generate labels for any subresources generated by the operator + labels := registry.LabelsForDevfileRegistry(devfileRegistry.Name) + + // Check if the service already exists, if not create a new one + result, err := r.ensureService(ctx, devfileRegistry, labels) + if result != nil { + return *result, err + } + + // If storage is enabled, create a persistent volume claim + if registry.IsStorageEnabled(devfileRegistry) { + // Check if the persistentvolumeclaim already exists, if not create a new one + result, err = r.ensurePVC(ctx, devfileRegistry, labels) + if result != nil { + return *result, err + } + } + + result, err = r.ensureDeployment(ctx, devfileRegistry, labels) + if result != nil { + return *result, err + } + + // Check to see if there's an old PVC that needs to be deleted + // Has to happen AFTER the deployment has been updated to remove the volume mount + if !registry.IsStorageEnabled(devfileRegistry) { + err = r.deletePVCIfNeeded(ctx, devfileRegistry) + if err != nil { + return ctrl.Result{}, err + } + } + + // Create/update the ingress/route for the devfile registry + hostname := devfileRegistry.Spec.K8s.IngressDomain + if config.ControllerCfg.IsOpenShift() { + // Check if the route exposing the devfile index exists + result, err = r.ensureDevfilesRoute(ctx, devfileRegistry, labels) + if result != nil { + return *result, err + } + + // If the route hostname was autodiscovered by OpenShift, need to retrieve the generated hostname. + // This is so that we can re-use the hostname in the second route and allows us to expose both routes under the same hostname + if hostname == "" { + // Get the hostname of the devfiles route + devfilesRoute := &routev1.Route{} + err = r.Get(ctx, types.NamespacedName{Name: devfileRegistry.Name + "-devfiles", Namespace: devfileRegistry.Namespace}, devfilesRoute) + if err != nil { + // Log an error, but requeue, as the controller's cached kube client likely hasn't registered the new route yet. + // See https://github.com/operator-framework/operator-sdk/issues/4013#issuecomment-707267616 for an explanation on why we requeue rather than error out here + log.Error(err, "Failed to get Route") + return ctrl.Result{Requeue: true}, nil + } + hostname = devfilesRoute.Spec.Host + } + + // Check if the route exposing the devfile index exists + result, err = r.ensureOCIRoute(ctx, devfileRegistry, hostname, labels) + if result != nil { + return *result, err + } + } else { + // Create/update the ingress for the devfile registry + hostname = registry.GetDevfileRegistryIngress(devfileRegistry) + result, err = r.ensureIngress(ctx, devfileRegistry, hostname, labels) + if result != nil { + return *result, err + } + } + + var devfileRegistryServer string + if registry.IsTLSEnabled(devfileRegistry) { + devfileRegistryServer = "https://" + hostname + } else { + devfileRegistryServer = "http://" + hostname + } + + if devfileRegistry.Status.URL != devfileRegistryServer { + // Check to see if the registry is active, and if so, update the status to reflect the URL + + err = util.WaitForServer(devfileRegistryServer, 30*time.Second) + if err != nil { + log.Error(err, "Devfile registry server failed to start after 30 seconds, requeing...") + return ctrl.Result{Requeue: true}, err + } + + // Update the status + devfileRegistry.Status.URL = devfileRegistryServer + err := r.Status().Update(ctx, devfileRegistry) + if err != nil { + log.Error(err, "Failed to update DevfileRegistry status") + return ctrl.Result{Requeue: true}, err + } + } return ctrl.Result{}, nil } func (r *DevfileRegistryReconciler) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). + // Check if we're running on OpenShift + isOS, err := cluster.IsOpenShift() + if err != nil { + return err + } + config.ControllerCfg.SetIsOpenShift(isOS) + + builder := ctrl.NewControllerManagedBy(mgr). For(®istryv1alpha1.DevfileRegistry{}). - Complete(r) + Owns(&appsv1.Deployment{}). + Owns(&corev1.Service{}). + Owns(&corev1.PersistentVolumeClaim{}). + Owns(&v1beta1.Ingress{}) + + // If on OpenShift, mark routes as owned by the controller + if config.ControllerCfg.IsOpenShift() { + builder.Owns(&routev1.Route{}) + } + + return builder.Complete(r) + } diff --git a/controllers/ensure.go b/controllers/ensure.go new file mode 100644 index 0000000..fbc3939 --- /dev/null +++ b/controllers/ensure.go @@ -0,0 +1,181 @@ +// +// Copyright (c) 2020 Red Hat, Inc. +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ +// +// SPDX-License-Identifier: EPL-2.0 +// +// Contributors: +// Red Hat, Inc. - initial API and implementation + +package controllers + +import ( + "context" + + registryv1alpha1 "github.com/devfile/registry-operator/api/v1alpha1" + "github.com/devfile/registry-operator/pkg/registry" + routev1 "github.com/openshift/api/route/v1" + "github.com/prometheus/common/log" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/api/extensions/v1beta1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +// ensureService ensures that a service for the devfile registry exists on the cluster and is up to date with the custom resource +func (r *DevfileRegistryReconciler) ensureService(ctx context.Context, cr *registryv1alpha1.DevfileRegistry, labels map[string]string) (*reconcile.Result, error) { + // Check if the service already exists, if not create a new one + svc := &corev1.Service{} + err := r.Get(ctx, types.NamespacedName{Name: registry.ServiceName(cr.Name), Namespace: cr.Namespace}, svc) + if err != nil && errors.IsNotFound(err) { + // Define a new service + pvc := registry.GenerateService(cr, r.Scheme, labels) + log.Info("Creating a new Service", "Service.Namespace", pvc.Namespace, "Service.Name", pvc.Name) + err = r.Create(ctx, pvc) + if err != nil { + log.Error(err, "Failed to create new Service", "Service.Namespace", pvc.Namespace, "Service.Name", pvc.Name) + return &ctrl.Result{}, err + } + return nil, nil + } else if err != nil { + log.Error(err, "Failed to get Service") + return &ctrl.Result{}, err + } + + return nil, nil +} + +// ensureDeployment ensures that a devfile registry deployment exists on the cluster and is up to date with the custom resource +func (r *DevfileRegistryReconciler) ensureDeployment(ctx context.Context, cr *registryv1alpha1.DevfileRegistry, labels map[string]string) (*reconcile.Result, error) { + dep := &appsv1.Deployment{} + err := r.Get(ctx, types.NamespacedName{Name: registry.DeploymentName(cr.Name), Namespace: cr.Namespace}, dep) + if err != nil && errors.IsNotFound(err) { + // Generate a new Deployment template and create it on the cluster + dep = registry.GenerateDeployment(cr, r.Scheme, labels) + + log.Info("Creating a new Deployment", "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name) + err = r.Create(ctx, dep) + if err != nil { + log.Error(err, "Failed to create new Deployment", "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name) + return &ctrl.Result{}, err + } + return nil, nil + } else if err != nil { + log.Error(err, "Failed to get Deployment") + return &ctrl.Result{}, err + } + + err = r.updateDeployment(ctx, cr, dep) + if err != nil { + log.Error(err, "Failed to update Deployment") + return &ctrl.Result{}, err + } + return nil, nil +} + +func (r *DevfileRegistryReconciler) ensurePVC(ctx context.Context, cr *registryv1alpha1.DevfileRegistry, labels map[string]string) (*reconcile.Result, error) { + // Check if the persistentvolumeclaim already exists, if not create a new one + pvc := &corev1.PersistentVolumeClaim{} + err := r.Get(ctx, types.NamespacedName{Name: registry.PVCName(cr.Name), Namespace: cr.Namespace}, pvc) + if err != nil && errors.IsNotFound(err) { + // Define a new PVC + pvc := registry.GeneratePVC(cr, r.Scheme, labels) + log.Info("Creating a new PersistentVolumeClaim", "PersistentVolumeClaim.Namespace", pvc.Namespace, "PersistentVolumeClaim.Name", pvc.Name) + err = r.Create(ctx, pvc) + if err != nil { + log.Error(err, "Failed to create new PersistentVolumeClaim", "PersistentVolumeClaim.Namespace", pvc.Namespace, "PersistentVolumeClaim.Name", pvc.Name) + return &ctrl.Result{}, err + } + return nil, nil + } else if err != nil { + log.Error(err, "Failed to get PersistentVolumeClaim") + return &ctrl.Result{}, err + } + + return nil, nil +} + +func (r *DevfileRegistryReconciler) ensureDevfilesRoute(ctx context.Context, cr *registryv1alpha1.DevfileRegistry, labels map[string]string) (*reconcile.Result, error) { + route := &routev1.Route{} + err := r.Get(ctx, types.NamespacedName{Name: registry.DevfilesRouteName(cr.Name), Namespace: cr.Namespace}, route) + if err != nil && errors.IsNotFound(err) { + // Define a new route exposing the devfile registry index + route := registry.GenerateDevfilesRoute(cr, r.Scheme, labels) + log.Info("Creating a new Route", "Route.Namespace", route.Namespace, "Route.Name", route.Name) + err = r.Create(ctx, route) + if err != nil { + log.Error(err, "Failed to create new Route", "Route.Namespace", route.Namespace, "Route.Name", route.Name) + return &ctrl.Result{}, err + } + return nil, nil + } else if err != nil { + log.Error(err, "Failed to get Route") + return &ctrl.Result{}, err + } + + err = r.updateDevfilesRoute(ctx, cr, route) + if err != nil { + log.Error(err, "Failed to update Route") + return &ctrl.Result{}, err + } + + return nil, nil +} + +func (r *DevfileRegistryReconciler) ensureOCIRoute(ctx context.Context, cr *registryv1alpha1.DevfileRegistry, hostname string, labels map[string]string) (*reconcile.Result, error) { + route := &routev1.Route{} + err := r.Get(ctx, types.NamespacedName{Name: registry.OCIRouteName(cr.Name), Namespace: cr.Namespace}, route) + if err != nil && errors.IsNotFound(err) { + // Define a new route exposing the devfile registry index + route = registry.GenerateOCIRoute(cr, hostname, r.Scheme, labels) + log.Info("Creating a new Route", "Route.Namespace", route.Namespace, "Route.Name", route.Name) + err = r.Create(ctx, route) + if err != nil { + log.Error(err, "Failed to create new Route", "Route.Namespace", route.Namespace, "Route.Name", route.Name) + return &ctrl.Result{}, err + } + return nil, nil + } else if err != nil { + log.Error(err, "Failed to get Route") + return &ctrl.Result{}, err + } + + err = r.updateOCIRoute(ctx, cr, route) + if err != nil { + log.Error(err, "Failed to update Route") + return &ctrl.Result{}, err + } + + return nil, nil +} + +func (r *DevfileRegistryReconciler) ensureIngress(ctx context.Context, cr *registryv1alpha1.DevfileRegistry, hostname string, labels map[string]string) (*reconcile.Result, error) { + ingress := &v1beta1.Ingress{} + err := r.Get(ctx, types.NamespacedName{Name: registry.IngressName(cr.Name), Namespace: cr.Namespace}, ingress) + if err != nil && errors.IsNotFound(err) { + // Define a new ingress exposing the devfile index and oci registry + ingress = registry.GenerateIngress(cr, hostname, r.Scheme, labels) + log.Info("Creating a new Ingress", "Ingress.Namespace", ingress.Namespace, "Ingress.Name", ingress.Name) + err = r.Create(ctx, ingress) + if err != nil { + log.Error(err, "Failed to create new Ingress", "Ingress.Namespace", ingress.Namespace, "Ingress.Name", ingress.Name) + return &ctrl.Result{}, err + } + return nil, nil + } else if err != nil { + log.Error(err, "Failed to get Ingress") + return &ctrl.Result{}, err + } + + err = r.updateIngress(ctx, cr, hostname, ingress) + if err != nil { + log.Error(err, "Failed to update Ingress") + return &ctrl.Result{}, err + } + return nil, nil +} diff --git a/controllers/update.go b/controllers/update.go new file mode 100644 index 0000000..7ffe4ec --- /dev/null +++ b/controllers/update.go @@ -0,0 +1,170 @@ +// +// Copyright (c) 2020 Red Hat, Inc. +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ +// +// SPDX-License-Identifier: EPL-2.0 +// +// Contributors: +// Red Hat, Inc. - initial API and implementation + +package controllers + +import ( + "context" + + registryv1alpha1 "github.com/devfile/registry-operator/api/v1alpha1" + "github.com/devfile/registry-operator/pkg/registry" + routev1 "github.com/openshift/api/route/v1" + "github.com/prometheus/common/log" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/api/extensions/v1beta1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// updateDeployment ensures that a devfile registry deployment exists on the cluster and is up to date with the custom resource +func (r *DevfileRegistryReconciler) updateDeployment(ctx context.Context, cr *registryv1alpha1.DevfileRegistry, dep *appsv1.Deployment) error { + // Check to see if the existing devfile registry deployment needs to be updated + needsUpdating := false + if dep.Spec.Template.Spec.Containers[0].Image != cr.Spec.DevfileIndexImage { + dep.Spec.Template.Spec.Containers[0].Image = cr.Spec.DevfileIndexImage + needsUpdating = true + } + ociImage := registry.GetOCIRegistryImage(cr) + if dep.Spec.Template.Spec.Containers[1].Image != ociImage { + dep.Spec.Template.Spec.Containers[1].Image = ociImage + needsUpdating = true + } + + if registry.IsStorageEnabled(cr) { + if dep.Spec.Template.Spec.Volumes[0].PersistentVolumeClaim == nil { + dep.Spec.Template.Spec.Volumes[0].VolumeSource = registry.GetDevfileRegistryVolumeSource(cr) + needsUpdating = true + } + } else { + if dep.Spec.Template.Spec.Volumes[0].PersistentVolumeClaim != nil { + dep.Spec.Template.Spec.Volumes[0].VolumeSource = registry.GetDevfileRegistryVolumeSource(cr) + needsUpdating = true + } + } + if needsUpdating { + log.Info("Updating the DevfileRegistry deployment") + return r.Update(ctx, dep) + } + return nil +} + +// updateOCIRoute checks to see if any of the fields in an existing devfile index route needs updating +func (r *DevfileRegistryReconciler) updateDevfilesRoute(ctx context.Context, cr *registryv1alpha1.DevfileRegistry, route *routev1.Route) error { + needsUpdating := false + + // Check to see if TLS fields were updated + if cr.Spec.TLS.Enabled != nil && *cr.Spec.TLS.Enabled { + if route.Spec.TLS == nil { + route.Spec.TLS = &routev1.TLSConfig{Termination: routev1.TLSTerminationEdge} + needsUpdating = true + } + } else { + if route.Spec.TLS != nil { + route.Spec.TLS = nil + needsUpdating = true + } + } + + if needsUpdating { + return r.Update(ctx, route) + } + return nil +} + +// updateOCIRoute checks to see if any of the fields in an existing oci registry route needs updating +func (r *DevfileRegistryReconciler) updateOCIRoute(ctx context.Context, cr *registryv1alpha1.DevfileRegistry, route *routev1.Route) error { + needsUpdating := false + + // Check to see if TLS fields were updated + if cr.Spec.TLS.Enabled != nil && *cr.Spec.TLS.Enabled { + if route.Spec.TLS == nil { + route.Spec.TLS = &routev1.TLSConfig{Termination: routev1.TLSTerminationEdge} + needsUpdating = true + } + } else { + if route.Spec.TLS != nil { + route.Spec.TLS = nil + needsUpdating = true + } + } + + if needsUpdating { + return r.Update(ctx, route) + } + return nil +} + +// updateIngress checks to see if any of the fields in an existing ingress resouorce need to be updated +func (r *DevfileRegistryReconciler) updateIngress(ctx context.Context, cr *registryv1alpha1.DevfileRegistry, hostname string, ingress *v1beta1.Ingress) error { + needsUpdating := false + // Check to see if TLS fields were updated + if registry.IsTLSEnabled(cr) { + if len(ingress.Spec.TLS) == 0 { + // TLS was toggled on, so enable it in the ingress spec + ingress.Spec.TLS = []v1beta1.IngressTLS{ + { + Hosts: []string{hostname}, + SecretName: cr.Spec.TLS.SecretName, + }, + } + needsUpdating = true + } + if ingress.Spec.TLS[0].SecretName != cr.Spec.TLS.SecretName { + // TLS secret name was updated, so update it in the ingress spec + ingress.Spec.TLS[0].SecretName = cr.Spec.TLS.SecretName + needsUpdating = true + } + } else { + if len(ingress.Spec.TLS) > 0 { + // TLS was disabled, so disable it in the ingress spec + ingress.Spec.TLS = []v1beta1.IngressTLS{} + needsUpdating = true + } + } + + // Check to see if the ingress domain was updated + if ingress.Spec.Rules[0].Host != hostname { + ingress.Spec.Rules[0].Host = hostname + + // If TLS is enabled, need to update the hostname there too + if registry.IsTLSEnabled(cr) { + ingress.Spec.TLS[0].Hosts = []string{hostname} + } + needsUpdating = true + } + + if needsUpdating { + return r.Update(ctx, ingress, &client.UpdateOptions{}) + } + + return nil +} + +// deletePVCIfNeeded deletes the PVC for the devfile registry if one exists and if persistent storage was disabled +func (r *DevfileRegistryReconciler) deletePVCIfNeeded(ctx context.Context, cr *registryv1alpha1.DevfileRegistry) error { + // Check to see if a PVC exists, if so, need to clean it up because storage was disabled + pvc := &corev1.PersistentVolumeClaim{} + err := r.Get(ctx, types.NamespacedName{Name: registry.PVCName(cr.Name), Namespace: cr.Namespace}, pvc) + if err != nil && !errors.IsNotFound(err) { + log.Error(err, "Error listing PersistentVolumeClaims") + return err + } else if pvc != nil && pvc.Name != "" { + log.Error(err, "Storage has been disabled ") + err = r.Delete(ctx, pvc) + if err != nil { + log.Error(err, "Error listing PersistentVolumeClaims") + return err + } + } + return nil +} diff --git a/go.mod b/go.mod index 23bb6e1..3a1fff2 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,10 @@ require ( github.com/go-logr/logr v0.1.0 github.com/onsi/ginkgo v1.12.1 github.com/onsi/gomega v1.10.1 + github.com/openshift/api v0.0.0-20200205133042-34f0ec8dab87 + github.com/prometheus/common v0.4.1 + gopkg.in/yaml.v2 v2.3.0 + k8s.io/api v0.18.6 k8s.io/apimachinery v0.18.6 k8s.io/client-go v0.18.6 sigs.k8s.io/controller-runtime v0.6.2 diff --git a/go.sum b/go.sum index d697bf9..540ba83 100644 --- a/go.sum +++ b/go.sum @@ -11,6 +11,7 @@ github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxB github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= @@ -18,7 +19,9 @@ github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= @@ -44,6 +47,7 @@ github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfc github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -55,6 +59,7 @@ github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZ github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= @@ -123,6 +128,7 @@ github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85n github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -149,6 +155,7 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -180,6 +187,7 @@ github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg= github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -190,6 +198,7 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= @@ -215,6 +224,7 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= @@ -227,6 +237,7 @@ github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1 h1:mFwc4LvZ0xpSvDZ3E+k8Yte0hLOMxXUlP+yXtJqkYfQ= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= @@ -235,12 +246,15 @@ github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/openshift/api v0.0.0-20200205133042-34f0ec8dab87 h1:L/fZlWB7DdYCd09r9LvBa44xRH42Dx80ybxfN1h5C8Y= +github.com/openshift/api v0.0.0-20200205133042-34f0ec8dab87/go.mod h1:fT6U/JfG8uZzemTRwZA2kBDJP5nWz7v05UHnty/D+pk= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= @@ -258,10 +272,12 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.11 h1:DhHlBtkHWPYi8O2y31JkK0TF+DGM+51OopZjH/Ia5qI= github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= @@ -279,6 +295,7 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= @@ -310,13 +327,19 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 h1:/Tl7pH94bvbAAHBdZJT947M/+gp0+CqQXDtMRC0fseo= golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -334,6 +357,7 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -355,6 +379,7 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -383,18 +408,25 @@ golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200115044656-831fdb1e1868/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.0.1 h1:xyiBuvkD2g5n7cYzx6u2sxQvsAy4QJsZFCzGVdzOXZ0= gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= +gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -415,6 +447,7 @@ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miE google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -440,18 +473,22 @@ gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +k8s.io/api v0.17.1/go.mod h1:zxiAc5y8Ngn4fmhWUtSxuUlkfz1ixT7j9wESokELzOg= k8s.io/api v0.18.6 h1:osqrAXbOQjkKIWDTjrqxWQ3w0GkKb1KA1XkUGHHYpeE= k8s.io/api v0.18.6/go.mod h1:eeyxr+cwCjMdLAmr2W3RyDI0VvTawSg/3RFFBEnmZGI= k8s.io/apiextensions-apiserver v0.18.6 h1:vDlk7cyFsDyfwn2rNAO2DbmUbvXy5yT5GE3rrqOzaMo= k8s.io/apiextensions-apiserver v0.18.6/go.mod h1:lv89S7fUysXjLZO7ke783xOwVTm6lKizADfvUM/SS/M= +k8s.io/apimachinery v0.17.1/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= k8s.io/apimachinery v0.18.6 h1:RtFHnfGNfd1N0LeSrKCUznz5xtUP1elRGvHJbL3Ntag= k8s.io/apimachinery v0.18.6/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= k8s.io/apiserver v0.18.6/go.mod h1:Zt2XvTHuaZjBz6EFYzpp+X4hTmgWGy8AthNVnTdm3Wg= k8s.io/client-go v0.18.6 h1:I+oWqJbibLSGsZj8Xs8F0aWVXJVIoUHWaaJV3kUN/Zw= k8s.io/client-go v0.18.6/go.mod h1:/fwtGLjYMS1MaM5oi+eXhKwG+1UHidUEXRh6cNsdO0Q= +k8s.io/code-generator v0.17.1/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s= k8s.io/code-generator v0.18.6/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c= k8s.io/component-base v0.18.6/go.mod h1:knSVsibPR5K6EW2XOjEHik6sdU5nCvKMrzMt2D4In14= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200114144118-36b2048a9120/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= @@ -459,14 +496,22 @@ k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog/v2 v2.0.0 h1:Foj74zO6RbjjP4hBEKjnYtjjAhGg4jNynUdYF6fJrok= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6 h1:Oh3Mzx5pJ+yIumsAD0MOECPVeXsVot0UkiaCGVyfGQY= k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20200603063816-c1c6865ac451 h1:v8ud2Up6QK1lNOKFgiIVrZdMg7MpmSnvtrOieolJKoE= k8s.io/utils v0.0.0-20200603063816-c1c6865ac451/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= +modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= +modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= +modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= +modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0= sigs.k8s.io/controller-runtime v0.6.2 h1:jkAnfdTYBpFwlmBn3pS5HFO06SfxvnTZ1p5PeEF/zAA= sigs.k8s.io/controller-runtime v0.6.2/go.mod h1:vhcq/rlnENJ09SIRp3EveTaZ0yqH526hjf9iJdbUJ/E= +sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e h1:4Z09Hglb792X0kfOBBJUPFEyvVfQWrYT/l8h5EKA6JQ= +sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= sigs.k8s.io/structured-merge-diff/v3 v3.0.0 h1:dOmIZBMfhcHS09XZkMyUgkq5trg3/jRyJYFZUiaOp8E= sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= diff --git a/main.go b/main.go index fc3b0a8..8e21f28 100644 --- a/main.go +++ b/main.go @@ -24,6 +24,8 @@ import ( registryv1alpha1 "github.com/devfile/registry-operator/api/v1alpha1" "github.com/devfile/registry-operator/controllers" + + routev1 "github.com/openshift/api/route/v1" // +kubebuilder:scaffold:imports ) @@ -34,7 +36,7 @@ var ( func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) - + utilruntime.Must(routev1.AddToScheme(scheme)) utilruntime.Must(registryv1alpha1.AddToScheme(scheme)) // +kubebuilder:scaffold:scheme } diff --git a/pkg/cluster/info.go b/pkg/cluster/info.go new file mode 100644 index 0000000..a6d5dd2 --- /dev/null +++ b/pkg/cluster/info.go @@ -0,0 +1,51 @@ +// +// Copyright (c) 2019-2020 Red Hat, Inc. +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ +// +// SPDX-License-Identifier: EPL-2.0 +// +// Contributors: +// Red Hat, Inc. - initial API and implementation +// + +package cluster + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/discovery" + "sigs.k8s.io/controller-runtime/pkg/client/config" +) + +// Borrowed from https://github.com/devfile/devworkspace-operator/blob/master/internal/cluster/info.go + +// IsOpenShift returns true if the operator is running on an OpenShift cluster +func IsOpenShift() (bool, error) { + kubeCfg, err := config.GetConfig() + if err != nil { + return false, err + } + discoveryClient, err := discovery.NewDiscoveryClientForConfig(kubeCfg) + if err != nil { + return false, err + } + apiList, err := discoveryClient.ServerGroups() + if err != nil { + return false, err + } + if findAPIGroup(apiList.Groups, "route.openshift.io") == nil { + return false, nil + } else { + return true, nil + } +} + +func findAPIGroup(source []metav1.APIGroup, apiName string) *metav1.APIGroup { + for i := 0; i < len(source); i++ { + if source[i].Name == apiName { + return &source[i] + } + } + return nil +} diff --git a/pkg/config/config.go b/pkg/config/config.go new file mode 100644 index 0000000..00c9200 --- /dev/null +++ b/pkg/config/config.go @@ -0,0 +1,27 @@ +// +// Copyright (c) 2020 Red Hat, Inc. +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ +// +// SPDX-License-Identifier: EPL-2.0 +// +// Contributors: +// Red Hat, Inc. - initial API and implementation + +package config + +// ControllerCfg logic borrowed from https://github.com/devfile/devworkspace-operator/blob/master/pkg/config/config.go +var ControllerCfg ControllerConfig + +type ControllerConfig struct { + isOpenShift bool +} + +func (c *ControllerConfig) IsOpenShift() bool { + return c.isOpenShift +} + +func (c *ControllerConfig) SetIsOpenShift(isOpenShift bool) { + c.isOpenShift = isOpenShift +} diff --git a/pkg/registry/defaults.go b/pkg/registry/defaults.go new file mode 100644 index 0000000..0896a35 --- /dev/null +++ b/pkg/registry/defaults.go @@ -0,0 +1,80 @@ +// +// Copyright (c) 2020 Red Hat, Inc. +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ +// +// SPDX-License-Identifier: EPL-2.0 +// +// Contributors: +// Red Hat, Inc. - initial API and implementation + +package registry + +import ( + registryv1alpha1 "github.com/devfile/registry-operator/api/v1alpha1" + corev1 "k8s.io/api/core/v1" +) + +const ( + // Default image:tags + DefaultDevfileIndexImage = "quay.io/devfile/metadata-server:next" + DefaultOCIRegistryImage = "registry:2.7.1" + + // Defaults/constants for devfile registry storages + DefaultDevfileRegistryVolumeSize = "1Gi" + DevfileRegistryVolumeEnabled = true + DevfileRegistryVolumeName = "devfile-registry-storage" + + DevfileRegistryTLSEnabled = true + + // Defaults/constants for devfile registry services + DevfileIndexPortName = "devfile-registry-metadata" + DevfileIndexPort = 8080 + OCIRegistryPortName = "oci-registry" + OCIRegistryPort = 5000 +) + +func GetOCIRegistryImage(cr *registryv1alpha1.DevfileRegistry) string { + if cr.Spec.OciRegistryImage != "" { + return cr.Spec.OciRegistryImage + } + return DefaultOCIRegistryImage +} + +func getDevfileRegistryVolumeSize(cr *registryv1alpha1.DevfileRegistry) string { + if cr.Spec.Storage.RegistryVolumeSize != "" { + return cr.Spec.Storage.RegistryVolumeSize + } + return DefaultDevfileRegistryVolumeSize +} + +func GetDevfileRegistryVolumeSource(cr *registryv1alpha1.DevfileRegistry) corev1.VolumeSource { + if IsStorageEnabled(cr) { + return corev1.VolumeSource{ + PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: PVCName(cr.Name), + }, + } + } + // If persistence is not enabled, return an empty dir volume source + return corev1.VolumeSource{} +} + +// IsStorageEnabled returns true if storage.enabled is set in the DevfileRegistry CR +// If it's not set, it returns true by default. +func IsStorageEnabled(cr *registryv1alpha1.DevfileRegistry) bool { + if cr.Spec.Storage.Enabled != nil { + return *cr.Spec.Storage.Enabled + } + return DevfileRegistryVolumeEnabled +} + +// IsTLSEnabled returns true if tls.enabled is set in the DevfileRegistry CR +// If it's not set, it returns true by default. +func IsTLSEnabled(cr *registryv1alpha1.DevfileRegistry) bool { + if cr.Spec.TLS.Enabled != nil { + return *cr.Spec.TLS.Enabled + } + return DevfileRegistryTLSEnabled +} diff --git a/pkg/registry/defaults_test.go b/pkg/registry/defaults_test.go new file mode 100644 index 0000000..1a182c0 --- /dev/null +++ b/pkg/registry/defaults_test.go @@ -0,0 +1,225 @@ +// +// Copyright (c) 2020 Red Hat, Inc. +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ +// +// SPDX-License-Identifier: EPL-2.0 +// +// Contributors: +// Red Hat, Inc. - initial API and implementation + +package registry + +import ( + "reflect" + "testing" + + registryv1alpha1 "github.com/devfile/registry-operator/api/v1alpha1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestIsTLSEnabled(t *testing.T) { + tlsEnabled := true + tlsDisabled := false + + tests := []struct { + name string + cr registryv1alpha1.DevfileRegistry + want bool + }{ + { + name: "Case 1: TLS enabled in DevfileRegistry CR", + cr: registryv1alpha1.DevfileRegistry{ + Spec: registryv1alpha1.DevfileRegistrySpec{ + TLS: registryv1alpha1.DevfileRegistrySpecTLS{ + Enabled: &tlsEnabled, + }, + }, + }, + want: true, + }, + { + name: "Case 2: TLS disabled in DevfileRegistry CR", + cr: registryv1alpha1.DevfileRegistry{ + Spec: registryv1alpha1.DevfileRegistrySpec{ + TLS: registryv1alpha1.DevfileRegistrySpecTLS{ + Enabled: &tlsDisabled, + }, + }, + }, + want: false, + }, + { + name: "Case 3: TLS not set, default set to true", + cr: registryv1alpha1.DevfileRegistry{ + Spec: registryv1alpha1.DevfileRegistrySpec{}, + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tlsSetting := IsTLSEnabled(&tt.cr) + if tlsSetting != tt.want { + t.Errorf("TestIsTLSEnabled error: tls value mismatch, expected: %v got: %v", tt.want, tlsSetting) + } + }) + } + +} + +func TestIsStorageEnabled(t *testing.T) { + storageEnabled := true + storageDisabled := false + + tests := []struct { + name string + cr registryv1alpha1.DevfileRegistry + want bool + }{ + { + name: "Case 1: Storage enabled in DevfileRegistry CR", + cr: registryv1alpha1.DevfileRegistry{ + Spec: registryv1alpha1.DevfileRegistrySpec{ + Storage: registryv1alpha1.DevfileRegistrySpecStorage{ + Enabled: &storageEnabled, + }, + }, + }, + want: true, + }, + { + name: "Case 2: Storage disabled in DevfileRegistry CR", + cr: registryv1alpha1.DevfileRegistry{ + Spec: registryv1alpha1.DevfileRegistrySpec{ + Storage: registryv1alpha1.DevfileRegistrySpecStorage{ + Enabled: &storageDisabled, + }, + }, + }, + want: false, + }, + { + name: "Case 3: Storage not set, default set to true", + cr: registryv1alpha1.DevfileRegistry{ + Spec: registryv1alpha1.DevfileRegistrySpec{}, + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tlsSetting := IsStorageEnabled(&tt.cr) + if tlsSetting != tt.want { + t.Errorf("TestIsStorageEnabled error: storage value mismatch, expected: %v got: %v", tt.want, tlsSetting) + } + }) + } + +} + +func TestGetDevfileRegistryVolumeSource(t *testing.T) { + storageEnabled := true + storageDisabled := false + crName := "devfileregistry-test" + + tests := []struct { + name string + cr registryv1alpha1.DevfileRegistry + want corev1.VolumeSource + }{ + { + name: "Case 1: Storage enabled in DevfileRegistry CR", + cr: registryv1alpha1.DevfileRegistry{ + ObjectMeta: metav1.ObjectMeta{ + Name: crName, + }, + Spec: registryv1alpha1.DevfileRegistrySpec{ + Storage: registryv1alpha1.DevfileRegistrySpecStorage{ + Enabled: &storageEnabled, + }, + }, + }, + want: corev1.VolumeSource{ + PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: PVCName(crName), + }, + }, + }, + { + name: "Case 2: Storage disabled in DevfileRegistry CR", + cr: registryv1alpha1.DevfileRegistry{ + ObjectMeta: metav1.ObjectMeta{ + Name: crName, + }, + Spec: registryv1alpha1.DevfileRegistrySpec{ + Storage: registryv1alpha1.DevfileRegistrySpecStorage{ + Enabled: &storageDisabled, + }, + }, + }, + want: corev1.VolumeSource{}, + }, + { + name: "Case 3: Storage not set, default set to true", + cr: registryv1alpha1.DevfileRegistry{ + ObjectMeta: metav1.ObjectMeta{ + Name: crName, + }, + Spec: registryv1alpha1.DevfileRegistrySpec{}, + }, + want: corev1.VolumeSource{ + PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: PVCName(crName), + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tlsSetting := GetDevfileRegistryVolumeSource(&tt.cr) + if !reflect.DeepEqual(tlsSetting, tt.want) { + t.Errorf("TestGetDevfileRegistryVolumeSource error: storage source mismatch, expected: %v got: %v", tt.want, tlsSetting) + } + }) + } + +} + +func TestGetDevfileRegistryVolumeSize(t *testing.T) { + tests := []struct { + name string + cr registryv1alpha1.DevfileRegistry + want string + }{ + { + name: "Case 1: Volume size set in DevfileRegistry CR", + cr: registryv1alpha1.DevfileRegistry{ + Spec: registryv1alpha1.DevfileRegistrySpec{ + Storage: registryv1alpha1.DevfileRegistrySpecStorage{ + RegistryVolumeSize: "5Gi", + }, + }, + }, + want: "5Gi", + }, + { + name: "Case 2: Volume size not set in DevfileRegistry CR", + cr: registryv1alpha1.DevfileRegistry{ + Spec: registryv1alpha1.DevfileRegistrySpec{}, + }, + want: DefaultDevfileRegistryVolumeSize, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + volSize := getDevfileRegistryVolumeSize(&tt.cr) + if volSize != tt.want { + t.Errorf("TestGetDevfileRegistryVolumeSize error: storage size mismatch, expected: %v got: %v", tt.want, volSize) + } + }) + } + +} diff --git a/pkg/registry/deployment.go b/pkg/registry/deployment.go new file mode 100644 index 0000000..22dbcad --- /dev/null +++ b/pkg/registry/deployment.go @@ -0,0 +1,116 @@ +// +// Copyright (c) 2020 Red Hat, Inc. +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ +// +// SPDX-License-Identifier: EPL-2.0 +// +// Contributors: +// Red Hat, Inc. - initial API and implementation + +package registry + +import ( + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/intstr" + ctrl "sigs.k8s.io/controller-runtime" + + registryv1alpha1 "github.com/devfile/registry-operator/api/v1alpha1" +) + +func GenerateDeployment(cr *registryv1alpha1.DevfileRegistry, scheme *runtime.Scheme, labels map[string]string) *appsv1.Deployment { + replicas := int32(1) + + dep := &appsv1.Deployment{ + ObjectMeta: generateObjectMeta(cr.Name, cr.Namespace, labels), + Spec: appsv1.DeploymentSpec{ + Replicas: &replicas, + Selector: &metav1.LabelSelector{ + MatchLabels: labels, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: labels, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Image: cr.Spec.DevfileIndexImage, + Name: "devfile-registry-bootstrap", + Ports: []corev1.ContainerPort{{ + ContainerPort: DevfileIndexPort, + }}, + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("100m"), + corev1.ResourceMemory: resource.MustParse("64Mi"), + }, + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("250m"), + corev1.ResourceMemory: resource.MustParse("128Mi"), + }, + }, + LivenessProbe: &corev1.Probe{ + Handler: corev1.Handler{ + HTTPGet: &corev1.HTTPGetAction{ + Path: "/devfiles/index.json", + Port: intstr.FromInt(DevfileIndexPort), + }, + }, + InitialDelaySeconds: int32(3), + PeriodSeconds: int32(3), + }, + ReadinessProbe: &corev1.Probe{ + Handler: corev1.Handler{ + HTTPGet: &corev1.HTTPGetAction{ + Path: "/devfiles/index.json", + Port: intstr.FromInt(DevfileIndexPort), + }, + }, + InitialDelaySeconds: int32(3), + PeriodSeconds: int32(3), + }, + }, + { + Image: GetOCIRegistryImage(cr), + Name: "oci-registry", + Ports: []corev1.ContainerPort{{ + ContainerPort: OCIRegistryPort, + }}, + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("100m"), + corev1.ResourceMemory: resource.MustParse("64Mi"), + }, + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("500m"), + corev1.ResourceMemory: resource.MustParse("256Mi"), + }, + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: DevfileRegistryVolumeName, + MountPath: "/var/lib/registry", + }, + }, + }, + }, + Volumes: []corev1.Volume{ + { + Name: DevfileRegistryVolumeName, + VolumeSource: GetDevfileRegistryVolumeSource(cr), + }, + }, + }, + }, + }, + } + // Set Memcached instance as the owner and controller + ctrl.SetControllerReference(cr, dep, scheme) + return dep +} diff --git a/pkg/registry/ingress.go b/pkg/registry/ingress.go new file mode 100644 index 0000000..e5d0a20 --- /dev/null +++ b/pkg/registry/ingress.go @@ -0,0 +1,70 @@ +// +// Copyright (c) 2020 Red Hat, Inc. +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ +// +// SPDX-License-Identifier: EPL-2.0 +// +// Contributors: +// Red Hat, Inc. - initial API and implementation + +package registry + +import ( + registryv1alpha1 "github.com/devfile/registry-operator/api/v1alpha1" + "k8s.io/api/extensions/v1beta1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/intstr" + ctrl "sigs.k8s.io/controller-runtime" +) + +func GenerateIngress(cr *registryv1alpha1.DevfileRegistry, host string, scheme *runtime.Scheme, labels map[string]string) *v1beta1.Ingress { + ingress := &v1beta1.Ingress{ + ObjectMeta: generateObjectMeta(IngressName(cr.Name), cr.Namespace, labels), + Spec: v1beta1.IngressSpec{ + Rules: []v1beta1.IngressRule{ + { + Host: host, + IngressRuleValue: v1beta1.IngressRuleValue{ + HTTP: &v1beta1.HTTPIngressRuleValue{ + Paths: []v1beta1.HTTPIngressPath{ + { + Path: "/", + Backend: v1beta1.IngressBackend{ + ServiceName: ServiceName(cr.Name), + ServicePort: intstr.FromInt(int(DevfileIndexPort)), + }, + }, + { + Path: "/v2", + Backend: v1beta1.IngressBackend{ + ServiceName: ServiceName(cr.Name), + ServicePort: intstr.FromInt(int(OCIRegistryPort)), + }, + }, + }, + }, + }, + }, + }, + }, + } + + if IsTLSEnabled(cr) && cr.Spec.TLS.SecretName != "" { + ingress.Spec.TLS = []v1beta1.IngressTLS{ + { + Hosts: []string{host}, + SecretName: cr.Spec.TLS.SecretName, + }, + } + } + + // Set DevfileRegistry instance as the owner and controller + ctrl.SetControllerReference(cr, ingress, scheme) + return ingress +} + +func GetDevfileRegistryIngress(cr *registryv1alpha1.DevfileRegistry) string { + return cr.Name + "." + cr.Spec.K8s.IngressDomain +} diff --git a/pkg/registry/naming.go b/pkg/registry/naming.go new file mode 100644 index 0000000..580f264 --- /dev/null +++ b/pkg/registry/naming.go @@ -0,0 +1,46 @@ +// +// Copyright (c) 2020 Red Hat, Inc. +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ +// +// SPDX-License-Identifier: EPL-2.0 +// +// Contributors: +// Red Hat, Inc. - initial API and implementation + +package registry + +// DeploymentName returns the name of the deployment object associated with the DevfileRegistry CR +// Just returns the CR name right now, but extracting to a function to avoid relying on that assumption in the future +func DeploymentName(devfileRegistryName string) string { + return devfileRegistryName +} + +// ServiceName returns the name of the service object associated with the DevfileRegistry CR +// Just returns the CR name right now, but extracting to a function to avoid relying on that assumption in the future +func ServiceName(devfileRegistryName string) string { + return devfileRegistryName +} + +// PVCName returns the name of the PVC object associated with the DevfileRegistry CR +// Just returns the CR name right now, but extracting to a function to avoid relying on that assumption in the future +func PVCName(devfileRegistryName string) string { + return devfileRegistryName +} + +// IngressName returns the name of the Ingress object associated with the DevfileRegistry CR +// Just returns the CR name right now, but extracting to a function to avoid relying on that assumption in the future +func IngressName(devfileRegistryName string) string { + return devfileRegistryName +} + +// DevfilesRouteName returns the name of the route object associated with the devfile index route +func DevfilesRouteName(devfileRegistryName string) string { + return devfileRegistryName + "-devfiles" +} + +// OCIRouteName returns the name of the route object associated with the OCI registry route +func OCIRouteName(devfileRegistryName string) string { + return devfileRegistryName + "-oci" +} diff --git a/pkg/registry/route.go b/pkg/registry/route.go new file mode 100644 index 0000000..7d663fc --- /dev/null +++ b/pkg/registry/route.go @@ -0,0 +1,82 @@ +// +// Copyright (c) 2020 Red Hat, Inc. +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ +// +// SPDX-License-Identifier: EPL-2.0 +// +// Contributors: +// Red Hat, Inc. - initial API and implementation + +package registry + +import ( + routev1 "github.com/openshift/api/route/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/intstr" + ctrl "sigs.k8s.io/controller-runtime" + + registryv1alpha1 "github.com/devfile/registry-operator/api/v1alpha1" +) + +// GenerateDevfilesRoute returns a route exposing the devfile registry index +func GenerateDevfilesRoute(cr *registryv1alpha1.DevfileRegistry, scheme *runtime.Scheme, labels map[string]string) *routev1.Route { + weight := int32(100) + + route := &routev1.Route{ + ObjectMeta: generateObjectMeta(DevfilesRouteName(cr.Name), cr.Namespace, labels), + Spec: routev1.RouteSpec{ + To: routev1.RouteTargetReference{ + Kind: "Service", + Name: ServiceName(cr.Name), + Weight: &weight, + }, + Port: &routev1.RoutePort{ + TargetPort: intstr.FromString(DevfileIndexPortName), + }, + Path: "/", + }, + } + + if cr.Spec.TLS.Enabled != nil && *cr.Spec.TLS.Enabled { + route.Spec.TLS = &routev1.TLSConfig{Termination: routev1.TLSTerminationEdge} + } + + // Set DevfileRegistry instance as the owner and controller + ctrl.SetControllerReference(cr, route, scheme) + return route +} + +// GenerateOCIRoute returns a route object for the OCI registry server +func GenerateOCIRoute(cr *registryv1alpha1.DevfileRegistry, host string, scheme *runtime.Scheme, labels map[string]string) *routev1.Route { + weight := int32(100) + + route := &routev1.Route{ + ObjectMeta: generateObjectMeta(OCIRouteName(cr.Name), cr.Namespace, labels), + Spec: routev1.RouteSpec{ + Host: host, + To: routev1.RouteTargetReference{ + Kind: "Service", + Name: ServiceName(cr.Name), + Weight: &weight, + }, + Port: &routev1.RoutePort{ + TargetPort: intstr.FromString(OCIRegistryPortName), + }, + Path: "/v2", + }, + } + + if cr.Spec.TLS.Enabled != nil && *cr.Spec.TLS.Enabled { + route.Spec.TLS = &routev1.TLSConfig{Termination: routev1.TLSTerminationEdge} + } + + if host != "" { + route.Spec.Host = host + } + + // Set DevfileRegistry instance as the owner and controller + ctrl.SetControllerReference(cr, route, scheme) + return route +} diff --git a/pkg/registry/service.go b/pkg/registry/service.go new file mode 100644 index 0000000..ec76e68 --- /dev/null +++ b/pkg/registry/service.go @@ -0,0 +1,44 @@ +// +// Copyright (c) 2020 Red Hat, Inc. +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ +// +// SPDX-License-Identifier: EPL-2.0 +// +// Contributors: +// Red Hat, Inc. - initial API and implementation + +package registry + +import ( + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + + registryv1alpha1 "github.com/devfile/registry-operator/api/v1alpha1" +) + +// GenerateDevfileRegistryService returns a devfileregistry Service object +func GenerateService(cr *registryv1alpha1.DevfileRegistry, scheme *runtime.Scheme, labels map[string]string) *corev1.Service { + svc := &corev1.Service{ + ObjectMeta: generateObjectMeta(ServiceName(cr.Name), cr.Namespace, labels), + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: DevfileIndexPortName, + Port: DevfileIndexPort, + }, + { + Name: OCIRegistryPortName, + Port: OCIRegistryPort, + }, + }, + Selector: labels, + }, + } + + // Set DevfileRegistry instance as the owner and controller + ctrl.SetControllerReference(cr, svc, scheme) + return svc +} diff --git a/pkg/registry/util.go b/pkg/registry/util.go new file mode 100644 index 0000000..8c79a95 --- /dev/null +++ b/pkg/registry/util.go @@ -0,0 +1,28 @@ +// +// Copyright (c) 2020 Red Hat, Inc. +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ +// +// SPDX-License-Identifier: EPL-2.0 +// +// Contributors: +// Red Hat, Inc. - initial API and implementation + +package registry + +import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + +func generateObjectMeta(name string, namespace string, labels map[string]string) metav1.ObjectMeta { + return metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Labels: labels, + } +} + +// LabelsForDevfileRegistry returns the labels for selecting the resources +// belonging to the given devfileregistry CR name. +func LabelsForDevfileRegistry(name string) map[string]string { + return map[string]string{"app": "devfileregistry", "devfileregistry_cr": name} +} diff --git a/pkg/registry/volume.go b/pkg/registry/volume.go new file mode 100644 index 0000000..375b877 --- /dev/null +++ b/pkg/registry/volume.go @@ -0,0 +1,40 @@ +// +// Copyright (c) 2020 Red Hat, Inc. +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ +// +// SPDX-License-Identifier: EPL-2.0 +// +// Contributors: +// Red Hat, Inc. - initial API and implementation + +package registry + +import ( + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + + registryv1alpha1 "github.com/devfile/registry-operator/api/v1alpha1" +) + +// GenerateDevfileRegistryPVC returns a PVC for providing storage on the OCI registry container +func GeneratePVC(cr *registryv1alpha1.DevfileRegistry, scheme *runtime.Scheme, labels map[string]string) *corev1.PersistentVolumeClaim { + pvc := &corev1.PersistentVolumeClaim{ + ObjectMeta: generateObjectMeta(cr.Name, cr.Namespace, labels), + Spec: corev1.PersistentVolumeClaimSpec{ + AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce}, + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceStorage: resource.MustParse(getDevfileRegistryVolumeSize(cr)), + }, + }, + }, + } + + // Set DevfileRegistry instance as the owner and controller + ctrl.SetControllerReference(cr, pvc, scheme) + return pvc +} diff --git a/pkg/util/util.go b/pkg/util/util.go new file mode 100644 index 0000000..f583872 --- /dev/null +++ b/pkg/util/util.go @@ -0,0 +1,40 @@ +// +// Copyright (c) 2019-2020 Red Hat, Inc. +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ +// +// SPDX-License-Identifier: EPL-2.0 +// +// Contributors: +// Red Hat, Inc. - initial API and implementation +// + +package util + +import ( + "crypto/tls" + "net/http" + "time" + + "k8s.io/apimachinery/pkg/util/wait" +) + +// Poll up to timeout seconds for pod to enter running state. +// Returns an error if the pod never enters the running state. +func WaitForServer(url string, timeout time.Duration) error { + return wait.PollImmediate(time.Second, timeout, func() (bool, error) { + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + client := &http.Client{Transport: tr} + resp, err := client.Get(url) + if err != nil { + return false, err + } + if resp.StatusCode == 200 { + return true, nil + } + return false, nil + }) +} From cff9ae459efc5460321f53ba2f49adb0635ef477 Mon Sep 17 00:00:00 2001 From: John Collier Date: Fri, 13 Nov 2020 15:24:47 -0500 Subject: [PATCH 3/9] Create integration test framework for the operator Implements a simple integration test framework for the Devfile Registy operator, based on the framework used for the devworkspace operator (see https://github.com/devfile/devworkspace-operator/tree/master/test/e2e) The following functionality is tested currently: - Simple devfile registry deploy - Simple devfile registry deploy with TLS enabled - Updating an existing devfile registry instance Signed-off-by: John Collier --- README.md | 2 +- tests/integration/cmd/devfileregistry_test.go | 91 +++++++++++ .../examples/create/devfileregistry-tls.yaml | 10 ++ .../examples/create/devfileregistry.yaml | 10 ++ .../examples/update/devfileregistry-new.yaml | 10 ++ .../examples/update/devfileregistry-old.yaml | 10 ++ tests/integration/pkg/client/client.go | 54 +++++++ tests/integration/pkg/client/kubectl.go | 43 ++++++ tests/integration/pkg/client/pod.go | 103 +++++++++++++ tests/integration/pkg/client/registry.go | 81 ++++++++++ tests/integration/pkg/config/config.go | 15 ++ tests/integration/pkg/deploy/controller.go | 70 +++++++++ tests/integration/pkg/deploy/deploy.go | 25 +++ .../pkg/tests/devfileregistry_tests.go | 145 ++++++++++++++++++ 14 files changed, 668 insertions(+), 1 deletion(-) create mode 100644 tests/integration/cmd/devfileregistry_test.go create mode 100644 tests/integration/examples/create/devfileregistry-tls.yaml create mode 100644 tests/integration/examples/create/devfileregistry.yaml create mode 100644 tests/integration/examples/update/devfileregistry-new.yaml create mode 100644 tests/integration/examples/update/devfileregistry-old.yaml create mode 100644 tests/integration/pkg/client/client.go create mode 100644 tests/integration/pkg/client/kubectl.go create mode 100644 tests/integration/pkg/client/pod.go create mode 100644 tests/integration/pkg/client/registry.go create mode 100644 tests/integration/pkg/config/config.go create mode 100644 tests/integration/pkg/deploy/controller.go create mode 100644 tests/integration/pkg/deploy/deploy.go create mode 100644 tests/integration/pkg/tests/devfileregistry_tests.go diff --git a/README.md b/README.md index 65eff32..06414cf 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Issue tracking repo: https://github.com/devfile/api with label area/registry The controller can be deployed to a cluster provided you are logged in with cluster-admin credentials: ```bash -export IMG=johncollier/registry-operator:v0.0.1 +export IMG=quay.io/devfile/registry-operator:next make install && make deploy ``` diff --git a/tests/integration/cmd/devfileregistry_test.go b/tests/integration/cmd/devfileregistry_test.go new file mode 100644 index 0000000..d3714a2 --- /dev/null +++ b/tests/integration/cmd/devfileregistry_test.go @@ -0,0 +1,91 @@ +// +// Copyright (c) 2020 Red Hat, Inc. +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ +// +// SPDX-License-Identifier: EPL-2.0 +// +// Contributors: +// Red Hat, Inc. - initial API and implementation +// + +package cmd + +import ( + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/devfile/registry-operator/tests/integration/pkg/config" + "github.com/devfile/registry-operator/tests/integration/pkg/deploy" + "github.com/devfile/registry-operator/tests/integration/pkg/tests" + + "github.com/devfile/registry-operator/tests/integration/pkg/client" + _ "github.com/devfile/registry-operator/tests/integration/pkg/tests" + "github.com/onsi/ginkgo" + "github.com/onsi/ginkgo/reporters" + "github.com/onsi/gomega" +) + +// Integration/e2e test logic based on https://github.com/devfile/devworkspace-operator/tree/master/test/e2e + +//Create Constant file +const ( + testResultsDirectory = "/tmp/artifacts" + jUnitOutputFilename = "junit-devfileregistry-operator.xml" +) + +//SynchronizedBeforeSuite blocks is executed before run all test suites +var _ = ginkgo.SynchronizedBeforeSuite(func() []byte { + fmt.Println("Starting to setup objects before run ginkgo suite") + namespace := os.Getenv("TEST_NAMESPACE") + if namespace != "" { + config.Namespace = namespace + } else { + config.Namespace = "registry-operator-system" + } + + k8sClient, err := client.NewK8sClient() + if err != nil { + fmt.Println("Failed to create k8s client") + panic(err) + } + + operator := deploy.NewDeployment(k8sClient) + + err = operator.CreateNamespace() + if err != nil { + panic(err) + } + + if err := operator.InstallCustomResourceDefinitions(); err != nil { + fmt.Println("Failed to install custom resources definitions on cluster") + panic(err) + } + + if err := operator.DeployDevfileRegistryOperator(); err != nil { + fmt.Println("Failed to deploy DevfileRegistry operator") + panic(err) + } + + tests.K8sClient, err = client.NewK8sClient() + if err != nil { + fmt.Println("Failed to create k8s client: " + err.Error()) + panic(err) + } + + return nil +}, func(data []byte) {}) + +func TestDevfileRegistryController(t *testing.T) { + gomega.RegisterFailHandler(ginkgo.Fail) + + fmt.Println("Creating ginkgo reporter for Test Harness: Junit and Debug Detail reporter") + var r []ginkgo.Reporter + r = append(r, reporters.NewJUnitReporter(filepath.Join(testResultsDirectory, jUnitOutputFilename))) + + fmt.Println("Running Devfile Registry integration tests...") + ginkgo.RunSpecsWithDefaultAndCustomReporters(t, "Devfile Registry Operator Tests", r) +} diff --git a/tests/integration/examples/create/devfileregistry-tls.yaml b/tests/integration/examples/create/devfileregistry-tls.yaml new file mode 100644 index 0000000..3128d1e --- /dev/null +++ b/tests/integration/examples/create/devfileregistry-tls.yaml @@ -0,0 +1,10 @@ +apiVersion: registry.devfile.io/v1alpha1 +kind: DevfileRegistry +metadata: + name: devfileregistry-tls +spec: + devfileIndexImage: quay.io/devfile/metadata-server:latest + storage: + enabled: false + tls: + enabled: true \ No newline at end of file diff --git a/tests/integration/examples/create/devfileregistry.yaml b/tests/integration/examples/create/devfileregistry.yaml new file mode 100644 index 0000000..751064c --- /dev/null +++ b/tests/integration/examples/create/devfileregistry.yaml @@ -0,0 +1,10 @@ +apiVersion: registry.devfile.io/v1alpha1 +kind: DevfileRegistry +metadata: + name: devfileregistry +spec: + devfileIndexImage: quay.io/devfile/metadata-server:latest + storage: + enabled: false + tls: + enabled: false \ No newline at end of file diff --git a/tests/integration/examples/update/devfileregistry-new.yaml b/tests/integration/examples/update/devfileregistry-new.yaml new file mode 100644 index 0000000..5080fd1 --- /dev/null +++ b/tests/integration/examples/update/devfileregistry-new.yaml @@ -0,0 +1,10 @@ +apiVersion: registry.devfile.io/v1alpha1 +kind: DevfileRegistry +metadata: + name: devfileregistry-update +spec: + devfileIndexImage: quay.io/devfile/metadata-server:latest + storage: + enabled: false + tls: + enabled: true \ No newline at end of file diff --git a/tests/integration/examples/update/devfileregistry-old.yaml b/tests/integration/examples/update/devfileregistry-old.yaml new file mode 100644 index 0000000..f8263da --- /dev/null +++ b/tests/integration/examples/update/devfileregistry-old.yaml @@ -0,0 +1,10 @@ +apiVersion: registry.devfile.io/v1alpha1 +kind: DevfileRegistry +metadata: + name: devfileregistry-update +spec: + devfileIndexImage: quay.io/devfile/metadata-server:latest + storage: + enabled: false + tls: + enabled: false \ No newline at end of file diff --git a/tests/integration/pkg/client/client.go b/tests/integration/pkg/client/client.go new file mode 100644 index 0000000..797917e --- /dev/null +++ b/tests/integration/pkg/client/client.go @@ -0,0 +1,54 @@ +// +// Copyright (c) 2020 Red Hat, Inc. +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ +// +// SPDX-License-Identifier: EPL-2.0 +// +// Contributors: +// Red Hat, Inc. - initial API and implementation +// + +package client + +import ( + "fmt" + "os" + + "k8s.io/client-go/kubernetes" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/config" +) + +type K8sClient struct { + kubeClient *kubernetes.Clientset + controllerClient client.Client +} + +// NewK8sClient creates kubernetes client wrapper with helper functions and direct access to k8s go client +func NewK8sClient() (*K8sClient, error) { + cfg, err := config.GetConfig() + if err != nil { + return nil, err + } + kubeClient, err := kubernetes.NewForConfig(cfg) + if err != nil { + return nil, err + } + + // Instantiate an instance of conroller-runtime client + controllerClient, err := client.New(config.GetConfigOrDie(), client.Options{}) + if err != nil { + fmt.Println("failed to create client") + os.Exit(1) + } + + h := &K8sClient{kubeClient: kubeClient, controllerClient: controllerClient} + return h, nil +} + +// Kube returns the clientset for Kubernetes upstream. +func (c *K8sClient) Kube() kubernetes.Interface { + return c.kubeClient +} diff --git a/tests/integration/pkg/client/kubectl.go b/tests/integration/pkg/client/kubectl.go new file mode 100644 index 0000000..c1448ec --- /dev/null +++ b/tests/integration/pkg/client/kubectl.go @@ -0,0 +1,43 @@ +// +// Copyright (c) 2020 Red Hat, Inc. +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ +// +// SPDX-License-Identifier: EPL-2.0 +// +// Contributors: +// Red Hat, Inc. - initial API and implementation +// + +package client + +import ( + "fmt" + "os/exec" + "strings" + + "github.com/devfile/registry-operator/tests/integration/pkg/config" +) + +// KubectlApplyResource applies resources on the cluster, corresponding to the specified file(s) +func (w *K8sClient) KubectlApplyResource(filePath string) (err error) { + cmd := exec.Command("kubectl", "apply", "--namespace", config.Namespace, "-f", filePath) + outBytes, err := cmd.CombinedOutput() + output := string(outBytes) + if err != nil && !strings.Contains(output, "AlreadyExists") { + fmt.Println(err) + } + return err +} + +// KubectlDeleteResource deletes the resources from the cluster that the specified file(s) correspond to +func (w *K8sClient) KubectlDeleteResource(filePath string) (err error) { + cmd := exec.Command("kubectl", "delete", "--namespace", config.Namespace, "-f", filePath) + outBytes, err := cmd.CombinedOutput() + output := string(outBytes) + if err != nil && !strings.Contains(output, "AlreadyExists") { + fmt.Println(err) + } + return err +} diff --git a/tests/integration/pkg/client/pod.go b/tests/integration/pkg/client/pod.go new file mode 100644 index 0000000..cca9227 --- /dev/null +++ b/tests/integration/pkg/client/pod.go @@ -0,0 +1,103 @@ +// +// Copyright (c) 2020 Red Hat, Inc. +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ +// +// SPDX-License-Identifier: EPL-2.0 +// +// Contributors: +// Red Hat, Inc. - initial API and implementation +// + +package client + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/devfile/registry-operator/tests/integration/pkg/config" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/wait" +) + +// WaitForPodRunningByLabel waits for the pod matching the specified label to become running +// An error is returned if the pod does not exist or the timeout is reached +func (w *K8sClient) WaitForPodRunningByLabel(label string) (deployed bool, err error) { + timeout := time.After(6 * time.Minute) + tick := time.Tick(1 * time.Second) + + for { + select { + case <-timeout: + return false, errors.New("timed out") + case <-tick: + err := w.WaitForRunningPodBySelector(config.Namespace, label, 3*time.Minute) + if err == nil { + return true, nil + } + } + } +} + +// WaitForRunningPodBySelector waits up to timeout seconds for all pods in 'namespace' with given 'selector' to enter running state. +// Returns an error if no pods are found or not all discovered pods enter running state. +func (w *K8sClient) WaitForRunningPodBySelector(namespace, selector string, timeout time.Duration) error { + podList, err := w.ListPods(namespace, selector) + if err != nil { + return err + } + if len(podList.Items) == 0 { + fmt.Println("Pod not created yet with selector " + selector + " in namespace " + namespace) + + return fmt.Errorf("Pod not created yet in %s with label %s", namespace, selector) + } + + for _, pod := range podList.Items { + fmt.Println("Pod " + pod.Name + " created in namespace " + namespace + "...Checking startup data.") + if err := w.waitForPodRunning(namespace, pod.Name, timeout); err != nil { + return err + } + } + + return nil +} + +// ListPods returns the list of currently scheduled or running pods in `namespace` with the given selector +func (w *K8sClient) ListPods(namespace, selector string) (*v1.PodList, error) { + listOptions := metav1.ListOptions{LabelSelector: selector} + podList, err := w.Kube().CoreV1().Pods(namespace).List(context.TODO(), listOptions) + + if err != nil { + return nil, err + } + return podList, nil +} + +// Poll up to timeout seconds for pod to enter running state. +// Returns an error if the pod never enters the running state. +func (w *K8sClient) waitForPodRunning(namespace, podName string, timeout time.Duration) error { + return wait.PollImmediate(time.Second, timeout, w.isPodRunning(podName, namespace)) +} + +// return a condition function that indicates whether the given pod is +// currently running +func (w *K8sClient) isPodRunning(podName, namespace string) wait.ConditionFunc { + return func() (bool, error) { + pod, _ := w.Kube().CoreV1().Pods(namespace).Get(context.TODO(), podName, metav1.GetOptions{}) + age := time.Since(pod.GetCreationTimestamp().Time).Seconds() + + switch pod.Status.Phase { + case v1.PodRunning: + fmt.Println("Pod started after", age, "seconds") + return true, nil + case v1.PodFailed, v1.PodSucceeded: + return false, nil + } + return false, nil + } +} diff --git a/tests/integration/pkg/client/registry.go b/tests/integration/pkg/client/registry.go new file mode 100644 index 0000000..eeb03f1 --- /dev/null +++ b/tests/integration/pkg/client/registry.go @@ -0,0 +1,81 @@ +// +// Copyright (c) 2020 Red Hat, Inc. +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ +// +// SPDX-License-Identifier: EPL-2.0 +// +// Contributors: +// Red Hat, Inc. - initial API and implementation +// + +package client + +import ( + "context" + "time" + + registryv1alpha1 "github.com/devfile/registry-operator/api/v1alpha1" + "github.com/devfile/registry-operator/tests/integration/pkg/config" + "gopkg.in/yaml.v2" + "k8s.io/apimachinery/pkg/util/wait" +) + +// GetRegistryInstance uses the Kubernetes REST API to retrieve the specified instance of the DevfileRegistry custom resource +// If there are any issues retrieving the resource or unmarshalling it, an error is returned +func (w *K8sClient) GetRegistryInstance(name string) (*registryv1alpha1.DevfileRegistry, error) { + data, err := w.kubeClient.RESTClient(). + Get(). + AbsPath("/apis/registry.devfile.io/v1alpha1"). + Namespace(config.Namespace). + Resource("devfileregistries"). + Name(name). + DoRaw(context.TODO()) + + if err != nil { + return nil, err + } + + // Unmarshall the struct + registry := ®istryv1alpha1.DevfileRegistry{} + err = yaml.Unmarshal(data, registry) + if err != nil { + return nil, err + } + + return registry, nil +} + +// WaitForRegistryInstance polls up to timeout seconds for the registry's server to become active (URL set in the status) +func (w *K8sClient) WaitForRegistryInstance(name string, timeout time.Duration) error { + return wait.PollImmediate(time.Second, timeout, func() (bool, error) { + devfileRegistry, err := w.GetRegistryInstance(name) + if err != nil { + return false, err + } + if devfileRegistry.Status.URL != "" { + return true, nil + } + return false, nil + }) +} + +// WaitForURLChange polls up to timeout seconds for the registry's URL to change in the status and returns it. +// If the URL doesn't change in the specified timeout, an error is returned +func (w *K8sClient) WaitForURLChange(name string, oldURL string, timeout time.Duration) (string, error) { + var newURL string + err := wait.PollImmediate(time.Second, timeout, func() (bool, error) { + devfileRegistry, err := w.GetRegistryInstance(name) + if err != nil { + return false, err + } + if devfileRegistry.Status.URL != oldURL { + newURL = devfileRegistry.Status.URL + return true, nil + } + return false, nil + }) + + return newURL, err +} diff --git a/tests/integration/pkg/config/config.go b/tests/integration/pkg/config/config.go new file mode 100644 index 0000000..c4edc3a --- /dev/null +++ b/tests/integration/pkg/config/config.go @@ -0,0 +1,15 @@ +// +// Copyright (c) 2019-2020 Red Hat, Inc. +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ +// +// SPDX-License-Identifier: EPL-2.0 +// +// Contributors: +// Red Hat, Inc. - initial API and implementation +// + +package config + +var Namespace string diff --git a/tests/integration/pkg/deploy/controller.go b/tests/integration/pkg/deploy/controller.go new file mode 100644 index 0000000..db3230f --- /dev/null +++ b/tests/integration/pkg/deploy/controller.go @@ -0,0 +1,70 @@ +// +// Copyright (c) 2020 Red Hat, Inc. +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ +// +// SPDX-License-Identifier: EPL-2.0 +// +// Contributors: +// Red Hat, Inc. - initial API and implementation +// + +package deploy + +import ( + "context" + "fmt" + "os/exec" + "strings" + + "k8s.io/apimachinery/pkg/api/errors" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/devfile/registry-operator/tests/integration/pkg/config" +) + +// CreateNamespace ensures that the namespace that the tests will run in already exiss +func (w *Deployment) CreateNamespace() error { + _, err := w.kubeClient.Kube().CoreV1().Namespaces().Create(context.TODO(), &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: config.Namespace, + }, + }, metav1.CreateOptions{}) + if errors.IsAlreadyExists(err) { + return nil + } + return err +} + +// DeployDevfileRegistryOperator deploys the DevfileRegistry operator +func (w *Deployment) DeployDevfileRegistryOperator() error { + label := "app.kubernetes.io/name=devfileregistry-operator" + cmd := exec.Command("make", "deploy") + output, err := cmd.CombinedOutput() + fmt.Println(string(output)) + if err != nil && !strings.Contains(string(output), "AlreadyExists") { + fmt.Println(err) + return err + } + + deploy, err := w.kubeClient.WaitForPodRunningByLabel(label) + fmt.Println("Devfile Regisry pod to be ready") + if !deploy || err != nil { + fmt.Println("Devfile Regisry not deployed") + return err + } + return nil +} + +func (w *Deployment) InstallCustomResourceDefinitions() error { + devfileRegistryCRD := exec.Command("make", "install") + output, err := devfileRegistryCRD.CombinedOutput() + if err != nil && !strings.Contains(string(output), "AlreadyExists") { + fmt.Println(err) + return err + } + return nil +} diff --git a/tests/integration/pkg/deploy/deploy.go b/tests/integration/pkg/deploy/deploy.go new file mode 100644 index 0000000..b5381e0 --- /dev/null +++ b/tests/integration/pkg/deploy/deploy.go @@ -0,0 +1,25 @@ +// +// Copyright (c) 2020 Red Hat, Inc. +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ +// +// SPDX-License-Identifier: EPL-2.0 +// +// Contributors: +// Red Hat, Inc. - initial API and implementation +// + +package deploy + +import ( + "github.com/devfile/registry-operator/tests/integration/pkg/client" +) + +type Deployment struct { + kubeClient *client.K8sClient +} + +func NewDeployment(kubeClient *client.K8sClient) *Deployment { + return &Deployment{kubeClient: kubeClient} +} diff --git a/tests/integration/pkg/tests/devfileregistry_tests.go b/tests/integration/pkg/tests/devfileregistry_tests.go new file mode 100644 index 0000000..d5d7865 --- /dev/null +++ b/tests/integration/pkg/tests/devfileregistry_tests.go @@ -0,0 +1,145 @@ +// +// Copyright (c) 2020 Red Hat, Inc. +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ +// +// SPDX-License-Identifier: EPL-2.0 +// +// Contributors: +// Red Hat, Inc. - initial API and implementation +// + +package tests + +import ( + "fmt" + "time" + + "github.com/devfile/registry-operator/pkg/util" + "github.com/devfile/registry-operator/tests/integration/pkg/client" + "github.com/onsi/ginkgo" + "github.com/onsi/gomega" +) + +// Integration/e2e test logic based on https://github.com/devfile/devworkspace-operator/tree/master/test/e2e + +var K8sClient *client.K8sClient + +var _ = ginkgo.Describe("[Create Devfile Registry resource]", func() { + ginkgo.It("Should deploy a devfile registry on to the cluster", func() { + crName := "devfileregistry" + label := "devfileregistry_cr=" + crName + + // Deploy the devfileregistry resource for this test case and wait for the pod to be running + err := K8sClient.KubectlApplyResource("tests/integration/examples/create/devfileregistry.yaml") + if err != nil { + ginkgo.Fail("Failed to create devfileregistry instance: " + err.Error()) + return + } + deploy, err := K8sClient.WaitForPodRunningByLabel(label) + if !deploy { + fmt.Println("Devfile Registry didn't start properly") + } + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + // Wait for the registry instance to become ready + err = K8sClient.WaitForRegistryInstance(crName, 30*time.Second) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + // Retrieve the registry URL and verify the server is up and running + registry, err := K8sClient.GetRegistryInstance(crName) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + err = util.WaitForServer(registry.Status.URL, 30*time.Second) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + }) + + var _ = ginkgo.AfterEach(func() { + K8sClient.KubectlDeleteResource("tests/integration/examples/create/devfileregistry.yaml") + }) +}) + +var _ = ginkgo.Describe("[Create Devfile Registry resource with TLS enabled]", func() { + ginkgo.It("Should deploy a devfile registry on to the cluster with HTTPS", func() { + crName := "devfileregistry-tls" + label := "devfileregistry_cr=" + crName + + // Deploy the devfileregistry resource for this test case and wait for the pod to be running + err := K8sClient.KubectlApplyResource("tests/integration/examples/create/devfileregistry-tls.yaml") + if err != nil { + ginkgo.Fail("Failed to create devfileregistry instance: " + err.Error()) + return + } + deploy, err := K8sClient.WaitForPodRunningByLabel(label) + if !deploy { + fmt.Println("Devfile Registry didn't start properly") + } + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + // Wait for the registry instance to become ready + err = K8sClient.WaitForRegistryInstance(crName, 30*time.Second) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + // Retrieve the registry URL and verify that its protocol is https + registry, err := K8sClient.GetRegistryInstance(crName) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + gomega.Expect(registry.Status.URL).To(gomega.ContainSubstring("https://")) + + // Verify that the server is accessible. + err = util.WaitForServer(registry.Status.URL, 30*time.Second) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + }) + + var _ = ginkgo.AfterEach(func() { + K8sClient.KubectlDeleteResource("tests/integration/examples/create/devfileregistry-tls.yaml") + }) +}) + +var _ = ginkgo.Describe("[Update Devfile Registry resource]", func() { + ginkgo.It("Should deploy a devfile registry on to the cluster and properly update it", func() { + crName := "devfileregistry-update" + label := "devfileregistry_cr=" + crName + + // Deploy the devfileregistry resource for this test case and wait for the pod to be running + err := K8sClient.KubectlApplyResource("tests/integration/examples/update/devfileregistry-old.yaml") + if err != nil { + ginkgo.Fail("Failed to create devfileregistry instance: " + err.Error()) + return + } + deploy, err := K8sClient.WaitForPodRunningByLabel(label) + if !deploy { + fmt.Println("Devfile Registry didn't start properly") + } + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + // Wait for the registry instance to become ready + err = K8sClient.WaitForRegistryInstance(crName, 30*time.Second) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + // Retrieve the registry URL and verify the server is up and running + registry, err := K8sClient.GetRegistryInstance(crName) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + err = util.WaitForServer(registry.Status.URL, 30*time.Second) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + // Update the devfileregistry resource for this test case + err = K8sClient.KubectlApplyResource("tests/integration/examples/update/devfileregistry-new.yaml") + if err != nil { + ginkgo.Fail("Failed to create devfileregistry instance: " + err.Error()) + return + } + + // Retrieve the registry URL and verify that its protocol is https + url, err := K8sClient.WaitForURLChange(crName, registry.Status.URL, 30*time.Second) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + gomega.Expect(url).To(gomega.ContainSubstring("https://")) + + // Verify that the server is accessible. + err = util.WaitForServer(url, 30*time.Second) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + }) + + var _ = ginkgo.AfterEach(func() { + K8sClient.KubectlDeleteResource("tests/integration/examples/update/devfileregistry-new.yaml") + }) +}) From 9898f861d908b05c5b6e4a3605d314f1a5026b0c Mon Sep 17 00:00:00 2001 From: John Collier Date: Fri, 13 Nov 2020 16:30:51 -0500 Subject: [PATCH 4/9] Quick cleanup before opening PR - Make sure IsTLSEnabled fucntion is used - Add sample registry.yaml file Signed-off-by: John Collier --- controllers/update.go | 4 ++-- pkg/registry/route.go | 4 ++-- registry.yaml | 6 ++++++ 3 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 registry.yaml diff --git a/controllers/update.go b/controllers/update.go index 7ffe4ec..4a75614 100644 --- a/controllers/update.go +++ b/controllers/update.go @@ -63,7 +63,7 @@ func (r *DevfileRegistryReconciler) updateDevfilesRoute(ctx context.Context, cr needsUpdating := false // Check to see if TLS fields were updated - if cr.Spec.TLS.Enabled != nil && *cr.Spec.TLS.Enabled { + if registry.IsTLSEnabled(cr) { if route.Spec.TLS == nil { route.Spec.TLS = &routev1.TLSConfig{Termination: routev1.TLSTerminationEdge} needsUpdating = true @@ -86,7 +86,7 @@ func (r *DevfileRegistryReconciler) updateOCIRoute(ctx context.Context, cr *regi needsUpdating := false // Check to see if TLS fields were updated - if cr.Spec.TLS.Enabled != nil && *cr.Spec.TLS.Enabled { + if registry.IsTLSEnabled(cr) { if route.Spec.TLS == nil { route.Spec.TLS = &routev1.TLSConfig{Termination: routev1.TLSTerminationEdge} needsUpdating = true diff --git a/pkg/registry/route.go b/pkg/registry/route.go index 7d663fc..e3617d5 100644 --- a/pkg/registry/route.go +++ b/pkg/registry/route.go @@ -39,7 +39,7 @@ func GenerateDevfilesRoute(cr *registryv1alpha1.DevfileRegistry, scheme *runtime }, } - if cr.Spec.TLS.Enabled != nil && *cr.Spec.TLS.Enabled { + if IsTLSEnabled(cr) { route.Spec.TLS = &routev1.TLSConfig{Termination: routev1.TLSTerminationEdge} } @@ -68,7 +68,7 @@ func GenerateOCIRoute(cr *registryv1alpha1.DevfileRegistry, host string, scheme }, } - if cr.Spec.TLS.Enabled != nil && *cr.Spec.TLS.Enabled { + if IsTLSEnabled(cr) { route.Spec.TLS = &routev1.TLSConfig{Termination: routev1.TLSTerminationEdge} } diff --git a/registry.yaml b/registry.yaml new file mode 100644 index 0000000..0e53d94 --- /dev/null +++ b/registry.yaml @@ -0,0 +1,6 @@ +apiVersion: registry.devfile.io/v1alpha1 +kind: DevfileRegistry +metadata: + name: devfile-registry +spec: + devfileIndexImage: quay.io/devfile/metadata-server:latest \ No newline at end of file From 2e60e0efac857b792f996469fe3b4617303bf41a Mon Sep 17 00:00:00 2001 From: John Collier Date: Fri, 20 Nov 2020 11:09:12 -0500 Subject: [PATCH 5/9] Don't expose the OCI registry anymore Now that we've switched over to using an nginx proxy to expose both the index server and OCI registry, we no longer need to expose the OCI registry directly. Signed-off-by: John Collier --- config/manager/manager.yaml | 2 +- controllers/devfileregistry_controller.go | 8 +---- controllers/ensure.go | 33 ++------------------ controllers/update.go | 27 ++--------------- pkg/registry/defaults.go | 4 +-- pkg/registry/deployment.go | 7 ++--- pkg/registry/ingress.go | 7 ----- pkg/registry/route.go | 37 ++--------------------- pkg/registry/service.go | 4 --- registry.yaml | 2 +- 10 files changed, 13 insertions(+), 118 deletions(-) diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index 012a808..cea1706 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -30,7 +30,7 @@ spec: - /manager args: - --enable-leader-election - image: quay.io/devfile/registry-operator:next + image: controller:latest imagePullPolicy: Always name: manager resources: diff --git a/controllers/devfileregistry_controller.go b/controllers/devfileregistry_controller.go index 658255f..1e9f482 100644 --- a/controllers/devfileregistry_controller.go +++ b/controllers/devfileregistry_controller.go @@ -104,7 +104,7 @@ func (r *DevfileRegistryReconciler) Reconcile(req ctrl.Request) (ctrl.Result, er hostname := devfileRegistry.Spec.K8s.IngressDomain if config.ControllerCfg.IsOpenShift() { // Check if the route exposing the devfile index exists - result, err = r.ensureDevfilesRoute(ctx, devfileRegistry, labels) + result, err = r.ensureRoute(ctx, devfileRegistry, labels) if result != nil { return *result, err } @@ -123,12 +123,6 @@ func (r *DevfileRegistryReconciler) Reconcile(req ctrl.Request) (ctrl.Result, er } hostname = devfilesRoute.Spec.Host } - - // Check if the route exposing the devfile index exists - result, err = r.ensureOCIRoute(ctx, devfileRegistry, hostname, labels) - if result != nil { - return *result, err - } } else { // Create/update the ingress for the devfile registry hostname = registry.GetDevfileRegistryIngress(devfileRegistry) diff --git a/controllers/ensure.go b/controllers/ensure.go index fbc3939..acc4782 100644 --- a/controllers/ensure.go +++ b/controllers/ensure.go @@ -100,12 +100,12 @@ func (r *DevfileRegistryReconciler) ensurePVC(ctx context.Context, cr *registryv return nil, nil } -func (r *DevfileRegistryReconciler) ensureDevfilesRoute(ctx context.Context, cr *registryv1alpha1.DevfileRegistry, labels map[string]string) (*reconcile.Result, error) { +func (r *DevfileRegistryReconciler) ensureRoute(ctx context.Context, cr *registryv1alpha1.DevfileRegistry, labels map[string]string) (*reconcile.Result, error) { route := &routev1.Route{} err := r.Get(ctx, types.NamespacedName{Name: registry.DevfilesRouteName(cr.Name), Namespace: cr.Namespace}, route) if err != nil && errors.IsNotFound(err) { // Define a new route exposing the devfile registry index - route := registry.GenerateDevfilesRoute(cr, r.Scheme, labels) + route := registry.GenerateRoute(cr, r.Scheme, labels) log.Info("Creating a new Route", "Route.Namespace", route.Namespace, "Route.Name", route.Name) err = r.Create(ctx, route) if err != nil { @@ -118,34 +118,7 @@ func (r *DevfileRegistryReconciler) ensureDevfilesRoute(ctx context.Context, cr return &ctrl.Result{}, err } - err = r.updateDevfilesRoute(ctx, cr, route) - if err != nil { - log.Error(err, "Failed to update Route") - return &ctrl.Result{}, err - } - - return nil, nil -} - -func (r *DevfileRegistryReconciler) ensureOCIRoute(ctx context.Context, cr *registryv1alpha1.DevfileRegistry, hostname string, labels map[string]string) (*reconcile.Result, error) { - route := &routev1.Route{} - err := r.Get(ctx, types.NamespacedName{Name: registry.OCIRouteName(cr.Name), Namespace: cr.Namespace}, route) - if err != nil && errors.IsNotFound(err) { - // Define a new route exposing the devfile registry index - route = registry.GenerateOCIRoute(cr, hostname, r.Scheme, labels) - log.Info("Creating a new Route", "Route.Namespace", route.Namespace, "Route.Name", route.Name) - err = r.Create(ctx, route) - if err != nil { - log.Error(err, "Failed to create new Route", "Route.Namespace", route.Namespace, "Route.Name", route.Name) - return &ctrl.Result{}, err - } - return nil, nil - } else if err != nil { - log.Error(err, "Failed to get Route") - return &ctrl.Result{}, err - } - - err = r.updateOCIRoute(ctx, cr, route) + err = r.updateRoute(ctx, cr, route) if err != nil { log.Error(err, "Failed to update Route") return &ctrl.Result{}, err diff --git a/controllers/update.go b/controllers/update.go index 4a75614..6393558 100644 --- a/controllers/update.go +++ b/controllers/update.go @@ -58,31 +58,8 @@ func (r *DevfileRegistryReconciler) updateDeployment(ctx context.Context, cr *re return nil } -// updateOCIRoute checks to see if any of the fields in an existing devfile index route needs updating -func (r *DevfileRegistryReconciler) updateDevfilesRoute(ctx context.Context, cr *registryv1alpha1.DevfileRegistry, route *routev1.Route) error { - needsUpdating := false - - // Check to see if TLS fields were updated - if registry.IsTLSEnabled(cr) { - if route.Spec.TLS == nil { - route.Spec.TLS = &routev1.TLSConfig{Termination: routev1.TLSTerminationEdge} - needsUpdating = true - } - } else { - if route.Spec.TLS != nil { - route.Spec.TLS = nil - needsUpdating = true - } - } - - if needsUpdating { - return r.Update(ctx, route) - } - return nil -} - -// updateOCIRoute checks to see if any of the fields in an existing oci registry route needs updating -func (r *DevfileRegistryReconciler) updateOCIRoute(ctx context.Context, cr *registryv1alpha1.DevfileRegistry, route *routev1.Route) error { +// updateRoute checks to see if any of the fields in an existing devfile index route needs updating +func (r *DevfileRegistryReconciler) updateRoute(ctx context.Context, cr *registryv1alpha1.DevfileRegistry, route *routev1.Route) error { needsUpdating := false // Check to see if TLS fields were updated diff --git a/pkg/registry/defaults.go b/pkg/registry/defaults.go index 0896a35..00a8d0e 100644 --- a/pkg/registry/defaults.go +++ b/pkg/registry/defaults.go @@ -18,7 +18,7 @@ import ( const ( // Default image:tags - DefaultDevfileIndexImage = "quay.io/devfile/metadata-server:next" + DefaultDevfileIndexImage = "quay.io/devfile/devfile-index:next" DefaultOCIRegistryImage = "registry:2.7.1" // Defaults/constants for devfile registry storages @@ -31,8 +31,6 @@ const ( // Defaults/constants for devfile registry services DevfileIndexPortName = "devfile-registry-metadata" DevfileIndexPort = 8080 - OCIRegistryPortName = "oci-registry" - OCIRegistryPort = 5000 ) func GetOCIRegistryImage(cr *registryv1alpha1.DevfileRegistry) string { diff --git a/pkg/registry/deployment.go b/pkg/registry/deployment.go index 22dbcad..88f2669 100644 --- a/pkg/registry/deployment.go +++ b/pkg/registry/deployment.go @@ -58,7 +58,7 @@ func GenerateDeployment(cr *registryv1alpha1.DevfileRegistry, scheme *runtime.Sc LivenessProbe: &corev1.Probe{ Handler: corev1.Handler{ HTTPGet: &corev1.HTTPGetAction{ - Path: "/devfiles/index.json", + Path: "/", Port: intstr.FromInt(DevfileIndexPort), }, }, @@ -68,7 +68,7 @@ func GenerateDeployment(cr *registryv1alpha1.DevfileRegistry, scheme *runtime.Sc ReadinessProbe: &corev1.Probe{ Handler: corev1.Handler{ HTTPGet: &corev1.HTTPGetAction{ - Path: "/devfiles/index.json", + Path: "/", Port: intstr.FromInt(DevfileIndexPort), }, }, @@ -79,9 +79,6 @@ func GenerateDeployment(cr *registryv1alpha1.DevfileRegistry, scheme *runtime.Sc { Image: GetOCIRegistryImage(cr), Name: "oci-registry", - Ports: []corev1.ContainerPort{{ - ContainerPort: OCIRegistryPort, - }}, Resources: corev1.ResourceRequirements{ Requests: corev1.ResourceList{ corev1.ResourceCPU: resource.MustParse("100m"), diff --git a/pkg/registry/ingress.go b/pkg/registry/ingress.go index e5d0a20..396f92c 100644 --- a/pkg/registry/ingress.go +++ b/pkg/registry/ingress.go @@ -36,13 +36,6 @@ func GenerateIngress(cr *registryv1alpha1.DevfileRegistry, host string, scheme * ServicePort: intstr.FromInt(int(DevfileIndexPort)), }, }, - { - Path: "/v2", - Backend: v1beta1.IngressBackend{ - ServiceName: ServiceName(cr.Name), - ServicePort: intstr.FromInt(int(OCIRegistryPort)), - }, - }, }, }, }, diff --git a/pkg/registry/route.go b/pkg/registry/route.go index e3617d5..7dd905c 100644 --- a/pkg/registry/route.go +++ b/pkg/registry/route.go @@ -20,8 +20,8 @@ import ( registryv1alpha1 "github.com/devfile/registry-operator/api/v1alpha1" ) -// GenerateDevfilesRoute returns a route exposing the devfile registry index -func GenerateDevfilesRoute(cr *registryv1alpha1.DevfileRegistry, scheme *runtime.Scheme, labels map[string]string) *routev1.Route { +// GenerateRoute returns a route exposing the devfile registry index +func GenerateRoute(cr *registryv1alpha1.DevfileRegistry, scheme *runtime.Scheme, labels map[string]string) *routev1.Route { weight := int32(100) route := &routev1.Route{ @@ -47,36 +47,3 @@ func GenerateDevfilesRoute(cr *registryv1alpha1.DevfileRegistry, scheme *runtime ctrl.SetControllerReference(cr, route, scheme) return route } - -// GenerateOCIRoute returns a route object for the OCI registry server -func GenerateOCIRoute(cr *registryv1alpha1.DevfileRegistry, host string, scheme *runtime.Scheme, labels map[string]string) *routev1.Route { - weight := int32(100) - - route := &routev1.Route{ - ObjectMeta: generateObjectMeta(OCIRouteName(cr.Name), cr.Namespace, labels), - Spec: routev1.RouteSpec{ - Host: host, - To: routev1.RouteTargetReference{ - Kind: "Service", - Name: ServiceName(cr.Name), - Weight: &weight, - }, - Port: &routev1.RoutePort{ - TargetPort: intstr.FromString(OCIRegistryPortName), - }, - Path: "/v2", - }, - } - - if IsTLSEnabled(cr) { - route.Spec.TLS = &routev1.TLSConfig{Termination: routev1.TLSTerminationEdge} - } - - if host != "" { - route.Spec.Host = host - } - - // Set DevfileRegistry instance as the owner and controller - ctrl.SetControllerReference(cr, route, scheme) - return route -} diff --git a/pkg/registry/service.go b/pkg/registry/service.go index ec76e68..ab6576b 100644 --- a/pkg/registry/service.go +++ b/pkg/registry/service.go @@ -29,10 +29,6 @@ func GenerateService(cr *registryv1alpha1.DevfileRegistry, scheme *runtime.Schem Name: DevfileIndexPortName, Port: DevfileIndexPort, }, - { - Name: OCIRegistryPortName, - Port: OCIRegistryPort, - }, }, Selector: labels, }, diff --git a/registry.yaml b/registry.yaml index 0e53d94..d8e4195 100644 --- a/registry.yaml +++ b/registry.yaml @@ -3,4 +3,4 @@ kind: DevfileRegistry metadata: name: devfile-registry spec: - devfileIndexImage: quay.io/devfile/metadata-server:latest \ No newline at end of file + devfileIndexImage: quay.io/devfile/devfile-index:next \ No newline at end of file From 482303288e02ff45a4c856ef45efd2473ad49c09 Mon Sep 17 00:00:00 2001 From: John Collier Date: Tue, 24 Nov 2020 13:01:47 -0500 Subject: [PATCH 6/9] Update integration test yamls to use new image Signed-off-by: John Collier --- tests/integration/examples/create/devfileregistry-tls.yaml | 2 +- tests/integration/examples/create/devfileregistry.yaml | 2 +- tests/integration/examples/update/devfileregistry-new.yaml | 2 +- tests/integration/examples/update/devfileregistry-old.yaml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/integration/examples/create/devfileregistry-tls.yaml b/tests/integration/examples/create/devfileregistry-tls.yaml index 3128d1e..e7f69d7 100644 --- a/tests/integration/examples/create/devfileregistry-tls.yaml +++ b/tests/integration/examples/create/devfileregistry-tls.yaml @@ -3,7 +3,7 @@ kind: DevfileRegistry metadata: name: devfileregistry-tls spec: - devfileIndexImage: quay.io/devfile/metadata-server:latest + devfileIndexImage: quay.io/devfile/devfile-index:next storage: enabled: false tls: diff --git a/tests/integration/examples/create/devfileregistry.yaml b/tests/integration/examples/create/devfileregistry.yaml index 751064c..2e306de 100644 --- a/tests/integration/examples/create/devfileregistry.yaml +++ b/tests/integration/examples/create/devfileregistry.yaml @@ -3,7 +3,7 @@ kind: DevfileRegistry metadata: name: devfileregistry spec: - devfileIndexImage: quay.io/devfile/metadata-server:latest + devfileIndexImage: quay.io/devfile/devfile-index:next storage: enabled: false tls: diff --git a/tests/integration/examples/update/devfileregistry-new.yaml b/tests/integration/examples/update/devfileregistry-new.yaml index 5080fd1..328038b 100644 --- a/tests/integration/examples/update/devfileregistry-new.yaml +++ b/tests/integration/examples/update/devfileregistry-new.yaml @@ -3,7 +3,7 @@ kind: DevfileRegistry metadata: name: devfileregistry-update spec: - devfileIndexImage: quay.io/devfile/metadata-server:latest + devfileIndexImage: quay.io/devfile/devfile-index:next storage: enabled: false tls: diff --git a/tests/integration/examples/update/devfileregistry-old.yaml b/tests/integration/examples/update/devfileregistry-old.yaml index f8263da..f1764cf 100644 --- a/tests/integration/examples/update/devfileregistry-old.yaml +++ b/tests/integration/examples/update/devfileregistry-old.yaml @@ -3,7 +3,7 @@ kind: DevfileRegistry metadata: name: devfileregistry-update spec: - devfileIndexImage: quay.io/devfile/metadata-server:latest + devfileIndexImage: quay.io/devfile/devfile-index:next storage: enabled: false tls: From 8ffb1582c60aadd38d95b2eb205177bde9700d44 Mon Sep 17 00:00:00 2001 From: John Collier Date: Thu, 26 Nov 2020 15:18:00 -0500 Subject: [PATCH 7/9] Address review comments - Updates the out-of-date DevfileRegistry CR sample - Removes the sample comments from fields in the DevfileRegistry custom resource definition. - Cleans up the function to delete old PVCs and updates its logic a bit to be more clear - Use defaults for readiness/liveness probes Signed-off-by: John Collier --- api/v1alpha1/devfileregistry_types.go | 7 ++--- ...registry.devfile.io_devfileregistries.yaml | 5 ++-- .../registry_v1alpha1_devfileregistry.yaml | 5 ++-- controllers/devfileregistry_controller.go | 10 +++---- controllers/update.go | 27 ++++++++++++------- pkg/registry/deployment.go | 4 --- 6 files changed, 27 insertions(+), 31 deletions(-) diff --git a/api/v1alpha1/devfileregistry_types.go b/api/v1alpha1/devfileregistry_types.go index 4dec44f..99169fe 100644 --- a/api/v1alpha1/devfileregistry_types.go +++ b/api/v1alpha1/devfileregistry_types.go @@ -17,12 +17,10 @@ import ( // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. +// Important: Run "make" to regenerate code after modifying this file // DevfileRegistrySpec defines the desired state of DevfileRegistry type DevfileRegistrySpec struct { - // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster - // Important: Run "make" to regenerate code after modifying this file - // Sets the container image containing devfile stacks to be deployed on the Devfile Registry DevfileIndexImage string `json:"devfileIndexImage,omitempty"` @@ -68,8 +66,7 @@ type DevfileRegistrySpecK8sOnly struct { // DevfileRegistryStatus defines the observed state of DevfileRegistry type DevfileRegistryStatus struct { - // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster - // Important: Run "make" to regenerate code after modifying this file + // URL is the exposed URL for the Devfile Registry, and is set in the status after the registry has become available. URL string `json:"url"` } diff --git a/config/crd/bases/registry.devfile.io_devfileregistries.yaml b/config/crd/bases/registry.devfile.io_devfileregistries.yaml index 6ee3404..f4e3a8f 100644 --- a/config/crd/bases/registry.devfile.io_devfileregistries.yaml +++ b/config/crd/bases/registry.devfile.io_devfileregistries.yaml @@ -95,9 +95,8 @@ spec: description: DevfileRegistryStatus defines the observed state of DevfileRegistry properties: url: - description: 'INSERT ADDITIONAL STATUS FIELD - define observed state - of cluster Important: Run "make" to regenerate code after modifying - this file' + description: URL is the exposed URL for the Devfile Registry, and is + set in the status after the registry has become available. type: string required: - url diff --git a/config/samples/registry_v1alpha1_devfileregistry.yaml b/config/samples/registry_v1alpha1_devfileregistry.yaml index 414341e..bf9c941 100644 --- a/config/samples/registry_v1alpha1_devfileregistry.yaml +++ b/config/samples/registry_v1alpha1_devfileregistry.yaml @@ -1,7 +1,6 @@ apiVersion: registry.devfile.io/v1alpha1 kind: DevfileRegistry metadata: - name: devfileregistry-sample + name: devfileregistry-tls spec: - bootstrapImage: quay.io/devfile/metadata-server:latest - ociRegistryImage: registry:latest \ No newline at end of file + devfileIndexImage: quay.io/devfile/devfile-index:next \ No newline at end of file diff --git a/controllers/devfileregistry_controller.go b/controllers/devfileregistry_controller.go index 1e9f482..e2c638b 100644 --- a/controllers/devfileregistry_controller.go +++ b/controllers/devfileregistry_controller.go @@ -92,12 +92,10 @@ func (r *DevfileRegistryReconciler) Reconcile(req ctrl.Request) (ctrl.Result, er } // Check to see if there's an old PVC that needs to be deleted - // Has to happen AFTER the deployment has been updated to remove the volume mount - if !registry.IsStorageEnabled(devfileRegistry) { - err = r.deletePVCIfNeeded(ctx, devfileRegistry) - if err != nil { - return ctrl.Result{}, err - } + // Has to happen AFTER the deployment has been updated. + err = r.deleteOldPVCIfNeeded(ctx, devfileRegistry) + if err != nil { + return ctrl.Result{}, err } // Create/update the ingress/route for the devfile registry diff --git a/controllers/update.go b/controllers/update.go index 6393558..b6118a4 100644 --- a/controllers/update.go +++ b/controllers/update.go @@ -128,19 +128,26 @@ func (r *DevfileRegistryReconciler) updateIngress(ctx context.Context, cr *regis } // deletePVCIfNeeded deletes the PVC for the devfile registry if one exists and if persistent storage was disabled -func (r *DevfileRegistryReconciler) deletePVCIfNeeded(ctx context.Context, cr *registryv1alpha1.DevfileRegistry) error { +func (r *DevfileRegistryReconciler) deleteOldPVCIfNeeded(ctx context.Context, cr *registryv1alpha1.DevfileRegistry) error { // Check to see if a PVC exists, if so, need to clean it up because storage was disabled - pvc := &corev1.PersistentVolumeClaim{} - err := r.Get(ctx, types.NamespacedName{Name: registry.PVCName(cr.Name), Namespace: cr.Namespace}, pvc) - if err != nil && !errors.IsNotFound(err) { - log.Error(err, "Error listing PersistentVolumeClaims") - return err - } else if pvc != nil && pvc.Name != "" { - log.Error(err, "Storage has been disabled ") - err = r.Delete(ctx, pvc) - if err != nil { + if !registry.IsStorageEnabled(cr) { + pvc := &corev1.PersistentVolumeClaim{} + err := r.Get(ctx, types.NamespacedName{Name: registry.PVCName(cr.Name), Namespace: cr.Namespace}, pvc) + if err != nil && errors.IsNotFound(err) { + // PVC not found, so there's no old PVC to delete. Just return nil, nothing to do. + return nil + } else if err != nil { + // Some other error occurred when listing PVCs, so log and return an error log.Error(err, "Error listing PersistentVolumeClaims") return err + } else { + // PVC found despite storage being disable, so delete it + log.Info(err, "Old PersistentVolumeClaim", pvc.Name, "found. Deleting it as storage has been disabled.") + err = r.Delete(ctx, pvc) + if err != nil { + log.Error(err, "Error deleting PersistentVolumeClaim", pvc.Name) + return err + } } } return nil diff --git a/pkg/registry/deployment.go b/pkg/registry/deployment.go index 88f2669..5b61cc0 100644 --- a/pkg/registry/deployment.go +++ b/pkg/registry/deployment.go @@ -62,8 +62,6 @@ func GenerateDeployment(cr *registryv1alpha1.DevfileRegistry, scheme *runtime.Sc Port: intstr.FromInt(DevfileIndexPort), }, }, - InitialDelaySeconds: int32(3), - PeriodSeconds: int32(3), }, ReadinessProbe: &corev1.Probe{ Handler: corev1.Handler{ @@ -72,8 +70,6 @@ func GenerateDeployment(cr *registryv1alpha1.DevfileRegistry, scheme *runtime.Sc Port: intstr.FromInt(DevfileIndexPort), }, }, - InitialDelaySeconds: int32(3), - PeriodSeconds: int32(3), }, }, { From 4dee5bd4d52df29edf52321abfafadc8345e0e5b Mon Sep 17 00:00:00 2001 From: John Collier Date: Thu, 26 Nov 2020 15:54:11 -0500 Subject: [PATCH 8/9] Support ingress on OpenShift if ingress domain set Signed-off-by: John Collier --- controllers/devfileregistry_controller.go | 24 ++++++++++------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/controllers/devfileregistry_controller.go b/controllers/devfileregistry_controller.go index e2c638b..f4e63ee 100644 --- a/controllers/devfileregistry_controller.go +++ b/controllers/devfileregistry_controller.go @@ -100,27 +100,23 @@ func (r *DevfileRegistryReconciler) Reconcile(req ctrl.Request) (ctrl.Result, er // Create/update the ingress/route for the devfile registry hostname := devfileRegistry.Spec.K8s.IngressDomain - if config.ControllerCfg.IsOpenShift() { + if config.ControllerCfg.IsOpenShift() && hostname == "" { // Check if the route exposing the devfile index exists result, err = r.ensureRoute(ctx, devfileRegistry, labels) if result != nil { return *result, err } - // If the route hostname was autodiscovered by OpenShift, need to retrieve the generated hostname. - // This is so that we can re-use the hostname in the second route and allows us to expose both routes under the same hostname - if hostname == "" { - // Get the hostname of the devfiles route - devfilesRoute := &routev1.Route{} - err = r.Get(ctx, types.NamespacedName{Name: devfileRegistry.Name + "-devfiles", Namespace: devfileRegistry.Namespace}, devfilesRoute) - if err != nil { - // Log an error, but requeue, as the controller's cached kube client likely hasn't registered the new route yet. - // See https://github.com/operator-framework/operator-sdk/issues/4013#issuecomment-707267616 for an explanation on why we requeue rather than error out here - log.Error(err, "Failed to get Route") - return ctrl.Result{Requeue: true}, nil - } - hostname = devfilesRoute.Spec.Host + // Get the hostname of the generated devfile route + devfilesRoute := &routev1.Route{} + err = r.Get(ctx, types.NamespacedName{Name: devfileRegistry.Name + "-devfiles", Namespace: devfileRegistry.Namespace}, devfilesRoute) + if err != nil { + // Log an error, but requeue, as the controller's cached kube client likely hasn't registered the new route yet. + // See https://github.com/operator-framework/operator-sdk/issues/4013#issuecomment-707267616 for an explanation on why we requeue rather than error out here + log.Error(err, "Failed to get Route") + return ctrl.Result{Requeue: true}, nil } + hostname = devfilesRoute.Spec.Host } else { // Create/update the ingress for the devfile registry hostname = registry.GetDevfileRegistryIngress(devfileRegistry) From c53bde4e90ca6d11e54bf5ea43873143606d349a Mon Sep 17 00:00:00 2001 From: John Collier Date: Fri, 27 Nov 2020 15:03:08 -0500 Subject: [PATCH 9/9] Address review comments - Fix pvc->svc typo - Clean up if statement check on deleting pvc Signed-off-by: John Collier --- controllers/ensure.go | 8 ++++---- controllers/update.go | 16 +++++++++------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/controllers/ensure.go b/controllers/ensure.go index acc4782..469bad5 100644 --- a/controllers/ensure.go +++ b/controllers/ensure.go @@ -34,11 +34,11 @@ func (r *DevfileRegistryReconciler) ensureService(ctx context.Context, cr *regis err := r.Get(ctx, types.NamespacedName{Name: registry.ServiceName(cr.Name), Namespace: cr.Namespace}, svc) if err != nil && errors.IsNotFound(err) { // Define a new service - pvc := registry.GenerateService(cr, r.Scheme, labels) - log.Info("Creating a new Service", "Service.Namespace", pvc.Namespace, "Service.Name", pvc.Name) - err = r.Create(ctx, pvc) + svc := registry.GenerateService(cr, r.Scheme, labels) + log.Info("Creating a new Service", "Service.Namespace", svc.Namespace, "Service.Name", svc.Name) + err = r.Create(ctx, svc) if err != nil { - log.Error(err, "Failed to create new Service", "Service.Namespace", pvc.Namespace, "Service.Name", pvc.Name) + log.Error(err, "Failed to create new Service", "Service.Namespace", svc.Namespace, "Service.Name", svc.Name) return &ctrl.Result{}, err } return nil, nil diff --git a/controllers/update.go b/controllers/update.go index b6118a4..9fed2b8 100644 --- a/controllers/update.go +++ b/controllers/update.go @@ -133,13 +133,15 @@ func (r *DevfileRegistryReconciler) deleteOldPVCIfNeeded(ctx context.Context, cr if !registry.IsStorageEnabled(cr) { pvc := &corev1.PersistentVolumeClaim{} err := r.Get(ctx, types.NamespacedName{Name: registry.PVCName(cr.Name), Namespace: cr.Namespace}, pvc) - if err != nil && errors.IsNotFound(err) { - // PVC not found, so there's no old PVC to delete. Just return nil, nothing to do. - return nil - } else if err != nil { - // Some other error occurred when listing PVCs, so log and return an error - log.Error(err, "Error listing PersistentVolumeClaims") - return err + if err != nil { + if errors.IsNotFound(err) { + // PVC not found, so there's no old PVC to delete. Just return nil, nothing to do. + return nil + } else { + // Some other error occurred when listing PVCs, so log and return an error + log.Error(err, "Error listing PersistentVolumeClaims") + return err + } } else { // PVC found despite storage being disable, so delete it log.Info(err, "Old PersistentVolumeClaim", pvc.Name, "found. Deleting it as storage has been disabled.")