diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ac4fe474..d551e40b 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,3 +4,4 @@ updates: directory: "/" schedule: interval: daily + target-branch: "master" diff --git a/Dockerfile b/Dockerfile index ab1e43d2..3ac68046 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Build the manager binary -FROM golang:1.19.3 as builder +FROM docker-na-public.artifactory.swg-devops.com/hyc-cloud-private-dockerhub-docker-remote/golang:1.23.2 as builder ARG GOARCH WORKDIR /workspace @@ -21,7 +21,7 @@ RUN CGO_ENABLED=0 GOOS=linux GO111MODULE=on go build -a -o manager main.go # Use distroless as minimal base image to package the manager binary # Refer to https://github.com/GoogleContainerTools/distroless for more details # FROM gcr.io/distroless/static:nonroot -FROM docker-na-public.artifactory.swg-devops.com/hyc-cloud-private-edge-docker-local/build-images/ubi8-minimal:latest +FROM docker-na-public.artifactory.swg-devops.com/hyc-cloud-private-edge-docker-local/build-images/ubi9-minimal:latest ARG VCS_REF ARG VCS_URL diff --git a/Makefile b/Makefile index 4493b9d9..f747d204 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,8 @@ KUBECTL ?= $(shell which kubectl) OPERATOR_SDK ?= $(shell which operator-sdk) OPM ?= $(shell which opm) +KUSTOMIZE ?= $(shell which kustomize) +KUSTOMIZE_VERSION=v3.8.7 ENVCRDS_DIR=$(shell pwd)/testcrds @@ -32,8 +34,10 @@ VERSION ?= $(shell git describe --exact-match 2> /dev/null || \ git describe --match=$(git rev-parse --short=8 HEAD) --always --dirty --abbrev=8) RELEASE_VERSION ?= $(shell cat ./version/version.go | grep "Version =" | awk '{ print $$3}' | tr -d '"') LATEST_VERSION ?= latest -OPERATOR_SDK_VERSION=v1.10.0 -YQ_VERSION=v4.3.1 +OPERATOR_SDK_VERSION=v1.32.0 +YQ_VERSION=v4.42.1 +DEFAULT_CHANNEL ?= v$(shell cat ./version/version.go | grep "Version =" | awk '{ print $$3}' | tr -d '"' | cut -d '.' -f1,2) +CHANNELS ?= $(DEFAULT_CHANNEL) LOCAL_OS := $(shell uname) ifeq ($(LOCAL_OS),Linux) @@ -61,7 +65,7 @@ else endif # Default image repo -QUAY_REGISTRY ?= quay.io/opencloudio +QUAY_REGISTRY ?= quay.io/luzarragaben ifeq ($(BUILD_LOCALLY),0) ARTIFACTORYA_REGISTRY ?= "docker-na-public.artifactory.swg-devops.com/hyc-cloud-private-integration-docker-local/ibmcom" @@ -80,12 +84,12 @@ OPERATOR_IMAGE_NAME ?= odlm # Current Operator bundle image name BUNDLE_IMAGE_NAME ?= odlm-operator-bundle # Current Operator version -OPERATOR_VERSION ?= 4.0.0 +OPERATOR_VERSION ?= 4.4.0 # Kind cluster name KIND_CLUSTER_NAME ?= "odlm" # Operator image tag for test -OPERATOR_TEST_TAG ?= dev-test +OPERATOR_TEST_TAG ?= nolm-controller-cleanup # Options for 'bundle-build' ifneq ($(origin CHANNELS), undefined) @@ -96,9 +100,6 @@ BUNDLE_DEFAULT_CHANNEL := --default-channel=$(DEFAULT_CHANNEL) endif BUNDLE_METADATA_OPTS ?= $(BUNDLE_CHANNELS) $(BUNDLE_DEFAULT_CHANNEL) -# Produce CRDs that work back to Kubernetes 1.11 (no version conversion) -CRD_OPTIONS ?= "crd:trivialVersions=true" - # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) ifeq (,$(shell go env GOBIN)) GOBIN=$(shell go env GOPATH)/bin @@ -108,7 +109,6 @@ endif ifeq ($(BUILD_LOCALLY),0) export CONFIG_DOCKER_TARGET = config-docker - export CONFIG_DOCKER_TARGET_QUAY = config-docker-quay endif include common/Makefile.common.mk @@ -134,21 +134,34 @@ else YQ=$(shell which yq) endif -# operator-sdk: -# ifneq ($(shell operator-sdk version | cut -d ',' -f1 | cut -d ':' -f2 | tr -d '"' | xargs | cut -d '.' -f1), v1) -# @{ \ -# if [ "$(shell ./bin/operator-sdk version | cut -d ',' -f1 | cut -d ':' -f2 | tr -d '"' | xargs)" != $(OPERATOR_SDK_VERSION) ]; then \ -# set -e ; \ -# mkdir -p bin ;\ -# echo "Downloading operator-sdk..." ;\ -# curl -sSLo ./bin/operator-sdk "https://github.com/operator-framework/operator-sdk/releases/download/$(OPERATOR_SDK_VERSION)/operator-sdk_$(LOCAL_OS)_$(LOCAL_ARCH)" ;\ -# chmod +x ./bin/operator-sdk ;\ -# fi ;\ -# } -# OPERATOR_SDK=$(realpath ./bin/operator-sdk) -# else -# OPERATOR_SDK=$(shell which operator-sdk) -# endif +kustomize: ## Install kustomize +ifeq (, $(shell which kustomize 2>/dev/null)) + @{ \ + set -e ;\ + mkdir -p bin ;\ + echo "Downloading kustomize ...";\ + curl -sSLo - https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize/$(KUSTOMIZE_VERSION)/kustomize_$(KUSTOMIZE_VERSION)_$(LOCAL_OS)_$(LOCAL_ARCH).tar.gz | tar xzf - -C bin/ ;\ + } +KUSTOMIZE=$(realpath ./bin/kustomize) +else +KUSTOMIZE=$(shell which kustomize) +endif + +operator-sdk: +ifneq ($(shell operator-sdk version | cut -d ',' -f1 | cut -d ':' -f2 | tr -d '"' | xargs | cut -d '.' -f1), v1) + @{ \ + if [ "$(shell ./bin/operator-sdk version | cut -d ',' -f1 | cut -d ':' -f2 | tr -d '"' | xargs)" != $(OPERATOR_SDK_VERSION) ]; then \ + set -e ; \ + mkdir -p bin ;\ + echo "Downloading operator-sdk..." ;\ + curl -sSLo ./bin/operator-sdk "https://github.com/operator-framework/operator-sdk/releases/download/$(OPERATOR_SDK_VERSION)/operator-sdk_$(LOCAL_OS)_$(LOCAL_ARCH)" ;\ + chmod +x ./bin/operator-sdk ;\ + fi ;\ + } +OPERATOR_SDK=$(realpath ./bin/operator-sdk) +else +OPERATOR_SDK=$(shell which operator-sdk) +endif code-dev: ## Run the default dev commands which are the go tidy, fmt, vet then execute the $ make code-gen @echo Running the common required commands for developments purposes @@ -182,19 +195,23 @@ deploy-e2e: kustomize ## Deploy controller in the configured Kubernetes cluster ##@ Generate code and manifests manifests: controller-gen ## Generate manifests e.g. CRD, RBAC etc. - $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=operand-deployment-lifecycle-manager webhook paths="./..." output:crd:artifacts:config=config/crd/bases + $(CONTROLLER_GEN) crd rbac:roleName=operand-deployment-lifecycle-manager webhook paths="./..." output:crd:artifacts:config=config/crd/bases generate: controller-gen ## Generate code e.g. API etc. $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." -bundle-manifests: +bundle-manifests: yq $(KUSTOMIZE) build config/manifests | $(OPERATOR_SDK) generate bundle \ -q --overwrite --version $(OPERATOR_VERSION) $(BUNDLE_METADATA_OPTS) $(OPERATOR_SDK) bundle validate ./bundle + $(YQ) eval-all -i '.spec.relatedImages |= load("config/manifests/bases/operand-deployment-lifecycle-manager.clusterserviceversion.yaml").spec.relatedImages' bundle/manifests/operand-deployment-lifecycle-manager.clusterserviceversion.yaml + @# Need to replace fields this way to avoid changing PROJECT name and CSV file name, which may or may not impact CICD automation + $(YQ) e -i '.annotations["operators.operatorframework.io.bundle.package.v1"] = "ibm-odlm"' bundle/metadata/annotations.yaml + sed -i'' s/operand-deployment-lifecycle-manager/ibm-odlm/ bundle.Dockerfile -generate-all: manifests kustomize operator-sdk ## Generate bundle manifests, metadata and package manifests +generate-all: yq manifests kustomize operator-sdk ## Generate bundle manifests, metadata and package manifests $(OPERATOR_SDK) generate kustomize manifests -q - - make bundle-manifests CHANNELS=v4.0 DEFAULT_CHANNEL=v4.0 + - make bundle-manifests CHANNELS=v4.4 DEFAULT_CHANNEL=v4.4 ##@ Test @@ -225,7 +242,7 @@ kind-start: kind echo "KIND Cluster already exists" && exit 0 || \ echo "Creating KIND Cluster" && \ ${KIND} create cluster --name ${KIND_CLUSTER_NAME} --config=./common/config/kind-config.yaml && \ - common/scripts/install-olm.sh 0.16.1 + common/scripts/install-olm.sh v0.24.0 kind-delete: @@ -245,8 +262,8 @@ build-operator-image: $(CONFIG_DOCKER_TARGET) ## Build the operator image. --build-arg GOARCH=$(LOCAL_ARCH) -f Dockerfile . build-operator-dev-image: ## Build the operator dev image. - @echo "Building the $(OPERATOR_IMAGE_NAME) docker image..." - @docker build -t $(OPERATOR_IMAGE_NAME):$(VERSION) \ + @echo "Building the $(DEV_REGISTRY)/$(OPERATOR_IMAGE_NAME) docker image..." + @docker build -t $(DEV_REGISTRY)/$(OPERATOR_IMAGE_NAME):$(VERSION) \ --build-arg VCS_REF=$(VCS_REF) --build-arg VCS_URL=$(VCS_URL) \ --build-arg GOARCH=$(LOCAL_ARCH) -f Dockerfile . @@ -260,7 +277,6 @@ build-test-operator-image: $(CONFIG_DOCKER_TARGET) ## Build the operator test im build-push-dev-image: build-operator-dev-image ## Build and push the operator dev images. @echo "Pushing the $(DEV_REGISTRY)/$(OPERATOR_IMAGE_NAME):$(VERSION) docker image to $(DEV_REGISTRY)..." - @docker tag $(OPERATOR_IMAGE_NAME):$(VERSION) $(DEV_REGISTRY)/$(OPERATOR_IMAGE_NAME):$(VERSION) @docker push $(DEV_REGISTRY)/$(OPERATOR_IMAGE_NAME):$(VERSION) build-push-image: $(CONFIG_DOCKER_TARGET) build-operator-image ## Build and push the operator images. @@ -268,20 +284,26 @@ build-push-image: $(CONFIG_DOCKER_TARGET) build-operator-image ## Build and pus @docker tag $(OPERATOR_IMAGE_NAME)-$(LOCAL_ARCH):$(VERSION) $(ARTIFACTORYA_REGISTRY)/$(OPERATOR_IMAGE_NAME)-$(LOCAL_ARCH):$(VERSION) @docker push $(ARTIFACTORYA_REGISTRY)/$(OPERATOR_IMAGE_NAME)-$(LOCAL_ARCH):$(VERSION) -build-dev-bundle-image: yq - @cp -f bundle/manifests/operand-deployment-lifecycle-manager.clusterserviceversion.yaml /tmp/operand-deployment-lifecycle-manager.clusterserviceversion.yaml - $(YQ) eval -i 'del(.spec.replaces)' bundle/manifests/operand-deployment-lifecycle-manager.clusterserviceversion.yaml - docker build -f bundle.Dockerfile -t $(QUAY_REGISTRY)/$(BUNDLE_IMAGE_NAME):dev . - docker push $(QUAY_REGISTRY)/$(BUNDLE_IMAGE_NAME):dev - @mv /tmp/operand-deployment-lifecycle-manager.clusterserviceversion.yaml bundle/manifests/operand-deployment-lifecycle-manager.clusterserviceversion.yaml - -build-push-bundle-image: $(CONFIG_DOCKER_TARGET_QUAY) build-bundle-image ## Build and push the bundle images. +build-push-bundle-image: yq + @docker build -f bundle.Dockerfile -t $(QUAY_REGISTRY)/$(BUNDLE_IMAGE_NAME)-$(LOCAL_ARCH):$(VERSION) . @echo "Pushing the $(BUNDLE_IMAGE_NAME) docker image for $(LOCAL_ARCH)..." @docker push $(QUAY_REGISTRY)/$(BUNDLE_IMAGE_NAME)-$(LOCAL_ARCH):$(VERSION) +build-catalog-source: + @opm -u docker index add --bundles $(QUAY_REGISTRY)/$(BUNDLE_IMAGE_NAME)-$(LOCAL_ARCH):$(VERSION) --tag $(QUAY_REGISTRY)/$(OPERATOR_IMAGE_NAME)-catalog:$(VERSION) + @docker push $(QUAY_REGISTRY)/$(OPERATOR_IMAGE_NAME)-catalog:$(VERSION) + +build-catalog: build-push-bundle-image build-catalog-source + multiarch-image: $(CONFIG_DOCKER_TARGET) ## Generate multiarch images for operator image. @MAX_PULLING_RETRY=20 RETRY_INTERVAL=30 common/scripts/multiarch_image.sh $(ARTIFACTORYA_REGISTRY) $(OPERATOR_IMAGE_NAME) $(VERSION) $(RELEASE_VERSION) +run-bundle: + $(OPERATOR_SDK) run bundle $(QUAY_REGISTRY)/$(BUNDLE_IMAGE_NAME)-$(LOCAL_ARCH):$(VERSION) --install-mode OwnNamespace + +cleanup-bundle: + $(OPERATOR_SDK) cleanup ibm-odlm + ##@ Help help: ## Display this help @echo "Usage:\n make \033[36m\033[0m" diff --git a/OWNERS b/OWNERS index dc89fb09..0a254210 100644 --- a/OWNERS +++ b/OWNERS @@ -4,9 +4,11 @@ approvers: - qpdpQ - bluzarraga - YCShen1010 +- Jeremy-Cheng-stack reviewers: - bitscuit - Daniel-Fan - qpdpQ - bluzarraga - YCShen1010 +- Jeremy-Cheng-stack diff --git a/PROJECT b/PROJECT index 83657404..a0883df7 100644 --- a/PROJECT +++ b/PROJECT @@ -1,5 +1,13 @@ +# Code generated by tool. DO NOT EDIT. +# This file is used to track the info used to scaffold your project +# and allow the plugins properly work. +# More info: https://book.kubebuilder.io/reference/project-config.html domain: ibm.com -layout: go.kubebuilder.io/v3 +layout: +- go.kubebuilder.io/v3 +plugins: + manifests.sdk.operatorframework.io/v2: {} + scorecard.sdk.operatorframework.io/v2: {} projectName: operand-deployment-lifecycle-manager repo: github.com/IBM/operand-deployment-lifecycle-manager resources: @@ -7,27 +15,33 @@ resources: domain: ibm.com group: operator kind: OperandRequest - path: github.com/IBM/operand-deployment-lifecycle-manager/api/v1alpha1 + path: github.com/IBM/operand-deployment-lifecycle-manager/v4/api/v1alpha1 version: v1alpha1 - controller: true domain: ibm.com group: operator kind: OperandRegistry - path: github.com/IBM/operand-deployment-lifecycle-manager/api/v1alpha1 + path: github.com/IBM/operand-deployment-lifecycle-manager/v4/api/v1alpha1 version: v1alpha1 - controller: true domain: ibm.com group: operator kind: OperandConfig - path: github.com/IBM/operand-deployment-lifecycle-manager/api/v1alpha1 + path: github.com/IBM/operand-deployment-lifecycle-manager/v4/api/v1alpha1 version: v1alpha1 - controller: true domain: ibm.com group: operator kind: OperandBindInfo - path: github.com/IBM/operand-deployment-lifecycle-manager/api/v1alpha1 + path: github.com/IBM/operand-deployment-lifecycle-manager/v4/api/v1alpha1 + version: v1alpha1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: ibm.com + group: operator + kind: OperatorConfig + path: github.com/IBM/operand-deployment-lifecycle-manager/v4/api/v1alpha1 version: v1alpha1 version: "3" -plugins: - manifests.sdk.operatorframework.io/v2: {} - scorecard.sdk.operatorframework.io/v2: {} diff --git a/api/v1alpha1/operandbindinfo_types.go b/api/v1alpha1/operandbindinfo_types.go index c481c4d5..f65fff28 100644 --- a/api/v1alpha1/operandbindinfo_types.go +++ b/api/v1alpha1/operandbindinfo_types.go @@ -38,7 +38,7 @@ const ( BindInfoFailed BindInfoPhase = "Failed" BindInfoInit BindInfoPhase = "Initialized" BindInfoUpdating BindInfoPhase = "Updating" - BindInfoWaiting BindInfoPhase = "Waiting for Secret and/or Configmap from provider" + BindInfoWaiting BindInfoPhase = "Waiting for Bindable resource from provider. One of: Secret, ConfigMap, Route, or Service" ) // OperandBindInfoSpec defines the desired state of OperandBindInfo. @@ -56,17 +56,52 @@ type OperandBindInfoSpec struct { Description string `json:"description,omitempty"` // The bindings section is used to specify information about the access/configuration data that is to be shared. // +optional - Bindings map[string]SecretConfigmap `json:"bindings,omitempty"` + Bindings map[string]Bindable `json:"bindings,omitempty"` } -// SecretConfigmap is a pair of Secret and/or Configmap. -type SecretConfigmap struct { +// Bindable is a Kubernetes resources to be shared from one namespace to another. +// List of supported resources are Secrets, Configmaps, Services, and Routes. +// Secrets and Configmaps will be copied such that a new Secret/Configmap with +// exactly the same data will be created in the target namespace. +// Services and Routes data will be copied into a configmap in the target +// namespace. +type Bindable struct { // The secret identifies an existing secret. if it exists, the ODLM will share to the namespace of the OperandRequest. // +optional Secret string `json:"secret,omitempty"` // The configmap identifies an existing configmap object. if it exists, the ODLM will share to the namespace of the OperandRequest. // +optional Configmap string `json:"configmap,omitempty"` + // Route data will be shared by copying it into a configmap which is then + // created in the target namespace + // +optional + Route *Route `json:"route,omitempty"` + // Service data will be shared by copying it into a configmap which is then + // created in the target namespace + // +optional + Service *ServiceData `json:"service,omitempty"` +} + +// Route represents the name and data inside an OpenShift route. +type Route struct { + // Name is the name of the OpenShift Route resource + // +optional + Name string `json:"name"` + // Data is a key-value pair where the value is a YAML path to a value in the + // OpenShift Route, e.g. .spec.host or .spec.tls.termination + // +optional + Data map[string]string `json:"data"` +} + +// ServiceData represents the name and data inside an Kubernetes Service. +type ServiceData struct { + // Name is the name of the Kubernetes Service resource + // +optional + Name string `json:"name"` + // Data is a key-value pair where the value is a YAML path to a value in the + // Kubernetes Service, e.g. .spec.ports[0]port + // +optional + Data map[string]string `json:"data"` } // OperandBindInfoStatus defines the observed state of OperandBindInfo. @@ -83,7 +118,7 @@ type OperandBindInfoStatus struct { // +kubebuilder:object:root=true // +kubebuilder:subresource:status -// OperandBindInfo is the Schema for the operandbindinfoes API. +// OperandBindInfo is the Schema for the operandbindinfoes API. Documentation For additional details regarding install parameters check https://ibm.biz/icpfs39install. License By installing this product you accept the license terms https://ibm.biz/icpfs39license // +kubebuilder:subresource:status // +kubebuilder:resource:path=operandbindinfos,shortName=opbi,scope=Namespaced // +kubebuilder:printcolumn:name="Age",type=date,JSONPath=.metadata.creationTimestamp @@ -95,7 +130,8 @@ type OperandBindInfo struct { metav1.ObjectMeta `json:"metadata,omitempty"` // +kubebuilder:pruning:PreserveUnknownFields - Spec OperandBindInfoSpec `json:"spec,omitempty"` + Spec OperandBindInfoSpec `json:"spec,omitempty"` + // +kubebuilder:pruning:PreserveUnknownFields Status OperandBindInfoStatus `json:"status,omitempty"` } diff --git a/api/v1alpha1/operandconfig_types.go b/api/v1alpha1/operandconfig_types.go index 0232f147..4a7fc94b 100644 --- a/api/v1alpha1/operandconfig_types.go +++ b/api/v1alpha1/operandconfig_types.go @@ -31,12 +31,17 @@ type OperandConfigSpec struct { Services []ConfigService `json:"services,omitempty"` } +// +kubebuilder:pruning:PreserveUnknownFields +type ExtensionWithMarker struct { + runtime.RawExtension `json:",inline"` +} + // ConfigService defines the configuration of the service. type ConfigService struct { // Name is the subscription name. Name string `json:"name"` // Spec is the configuration map of custom resource. - Spec map[string]runtime.RawExtension `json:"spec,omitempty"` + Spec map[string]ExtensionWithMarker `json:"spec,omitempty"` // State is a flag to enable or disable service. State string `json:"state,omitempty"` // Resources is used to specify the kubernetes resources that are needed for the service. @@ -44,6 +49,7 @@ type ConfigService struct { Resources []ConfigResource `json:"resources,omitempty"` } +// +kubebuilder:pruning:PreserveUnknownFields // ConfigResource defines the resource needed for the service type ConfigResource struct { // Name is the resource name. @@ -70,6 +76,83 @@ type ConfigResource struct { // +nullable // +optional Data *runtime.RawExtension `json:"data,omitempty"` + // OwnerReferences is the list of owner references. + // +optional + OwnerReferences []OwnerReference `json:"ownerReferences,omitempty"` + // OptionalFields is the list of fields that could be updated additionally. + // +optional + OptionalFields []OptionalField `json:"optionalFields,omitempty"` +} + +// +kubebuilder:pruning:PreserveUnknownFields +// OptionalField defines the optional field for the resource. +type OptionalField struct { + // Path is the json path of the field. + Path string `json:"path"` + // Operation is the operation of the field. + Operation Operation `json:"operation"` + // MatchExpressions is the match expression of the field. + // +optional + MatchExpressions []MatchExpression `json:"matchExpressions,omitempty"` + // ValueFrom is the field value from the object + // +optional + ValueFrom *ValueFrom `json:"valueFrom,omitempty"` +} + +// +kubebuilder:pruning:PreserveUnknownFields +// MatchExpression defines the match expression of the field. +type MatchExpression struct { + // Key is the key of the field. + Key string `json:"key"` + // Operator is the operator of the field. + Operator ExpressionOperator `json:"operator"` + // Values is the values of the field. + // +optional + Values []string `json:"values,omitempty"` + // ObjectRef is the reference of the object. + // +optional + ObjectRef *ObjectRef `json:"objectRef,omitempty"` +} + +// ObjectRef defines the reference of the object. +type ObjectRef struct { + // APIVersion is the version of the object. + APIVersion string `json:"apiVersion"` + // Kind is the kind of the object. + Kind string `json:"kind"` + // Name is the name of the object. + Name string `json:"name"` + // Namespace is the namespace of the object. + // +optional + Namespace string `json:"namespace"` +} + +// ValueFrom defines the field value from the object. +type ValueFrom struct { + // Path is the json path of the field. + Path string `json:"path"` + // ObjectRef is the reference of the object. + // +optional + ObjectRef *ObjectRef `json:"objectRef,omitempty"` +} + +type OwnerReference struct { + // API version of the referent. + APIVersion string `json:"apiVersion"` + // Kind of the referent. + Kind string `json:"kind"` + // Name of the referent. + Name string `json:"name"` + // If true, this reference points to the managing controller. + // Default is false. + // +optional + Controller *bool `json:"controller,omitempty"` + // If true, AND if the owner has the "foregroundDeletion" finalizer, then + // the owner cannot be deleted from the key-value store until this + // reference is removed. + // Defaults to false. + // +optional + BlockOwnerDeletion *bool `json:"blockOwnerDeletion,omitempty"` } // OperandConfigStatus defines the observed state of OperandConfig. @@ -89,7 +172,7 @@ type CrStatus struct { CrStatus map[string]ServicePhase `json:"customResourceStatus,omitempty"` } -// OperandConfig is the Schema for the operandconfigs API. +// OperandConfig is the Schema for the operandconfigs API. Documentation For additional details regarding install parameters check https://ibm.biz/icpfs39install. License By installing this product you accept the license terms https://ibm.biz/icpfs39license // +kubebuilder:object:root=true // +kubebuilder:subresource:status // +kubebuilder:resource:path=operandconfigs,shortName=opcon,scope=Namespaced @@ -102,7 +185,8 @@ type OperandConfig struct { metav1.ObjectMeta `json:"metadata,omitempty"` // +kubebuilder:pruning:PreserveUnknownFields - Spec OperandConfigSpec `json:"spec,omitempty"` + Spec OperandConfigSpec `json:"spec,omitempty"` + // +kubebuilder:pruning:PreserveUnknownFields Status OperandConfigStatus `json:"status,omitempty"` } @@ -132,6 +216,26 @@ const ( ServiceNone ServicePhase = "" ) +// Operation defines the operation of the field. +type Operation string + +// Operation type. +const ( + OperationAdd Operation = "add" + OperationRemove Operation = "remove" +) + +// Operator defines the operator type. +type ExpressionOperator string + +// Operator type. +const ( + OperatorIn ExpressionOperator = "In" + OperatorNotIn ExpressionOperator = "NotIn" + OperatorExists ExpressionOperator = "Exists" + OperatorDoesNotExist ExpressionOperator = "DoesNotExist" +) + // GetService obtains the service definition with the operand name. func (r *OperandConfig) GetService(operandName string) *ConfigService { for _, s := range r.Spec.Services { diff --git a/api/v1alpha1/operandregistry_types.go b/api/v1alpha1/operandregistry_types.go index 6daf785b..1b939d0a 100644 --- a/api/v1alpha1/operandregistry_types.go +++ b/api/v1alpha1/operandregistry_types.go @@ -27,6 +27,7 @@ import ( // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. // Operator defines the desired state of Operators. +// +kubebuilder:pruning:PreserveUnknownFields type Operator struct { // A unique name for the operator whose operand may be deployed. Name string `json:"name"` @@ -40,6 +41,7 @@ type Operator struct { // Valid values are: // - "namespace" (default): operator is deployed in namespace of OperandRegistry; // - "cluster": operator is deployed in "openshift-operators" namespace; + // - "no-op": operator is not supported to be fresh deployed; // +optional InstallMode string `json:"installMode,omitempty"` // The namespace in which operator should be deployed when InstallMode is empty or set to "namespace". @@ -56,6 +58,9 @@ type Operator struct { PackageName string `json:"packageName"` // Name of the channel to track. Channel string `json:"channel"` + // List of channels to fallback when the main channel is not available. + // +optional + FallbackChannels []string `json:"fallbackChannels,omitempty"` // Description of a common service. // +optional Description string `json:"description,omitempty"` @@ -71,12 +76,11 @@ type Operator struct { // SubscriptionConfig is used to override operator configuration. // +optional SubscriptionConfig *olmv1alpha1.SubscriptionConfig `json:"subscriptionConfig,omitempty"` - // SupportStatus is used to indicate the support status for services. - // Valid values are: - // - "continuous" (default): operator is supported to be fresh deployed via OperandRequest from scratch - // - "maintained" operator is not supported to be fresh deployed via OperandRequest, only upgrade and deletion are allowed + // OperatorConfig is the name of the OperatorConfig // +optional - SupportStatus string `json:"supportStatus,omitempty"` + OperatorConfig string `json:"operatorConfig,omitempty"` + // UserManaged is a flag to indicate whether operator is managed by user + UserManaged bool `json:"userManaged,omitempty"` } // +kubebuilder:validation:Enum=public;private @@ -96,13 +100,8 @@ const ( InstallModeCluster string = "cluster" // InstallModeNamespace means install the operator in one namespace mode. InstallModeNamespace string = "namespace" -) - -const ( - // MaintainedSupportStatus means NOT supporting fresh deployment for the operator - MaintainedSupportStatus string = "maintained" - // ContinuousSupportStatus means supporting fresh deployment for the operator - ContinuousSupportStatus string = "continuous" + // InstallModeNoop means not create the subscription for the operator + InstallModeNoop string = "no-op" ) // OperandRegistrySpec defines the desired state of OperandRegistry. @@ -154,13 +153,14 @@ type ReconcileRequest struct { // +kubebuilder:printcolumn:name="Created At",type=string,JSONPath=.metadata.creationTimestamp // +operator-sdk:csv:customresourcedefinitions:displayName="OperandRegistry" -// OperandRegistry is the Schema for the operandregistries API. +// OperandRegistry is the Schema for the operandregistries API. Documentation For additional details regarding install parameters check https://ibm.biz/icpfs39install. License By installing this product you accept the license terms https://ibm.biz/icpfs39license type OperandRegistry struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` // +kubebuilder:pruning:PreserveUnknownFields - Spec OperandRegistrySpec `json:"spec,omitempty"` + Spec OperandRegistrySpec `json:"spec,omitempty"` + // +kubebuilder:pruning:PreserveUnknownFields Status OperandRegistryStatus `json:"status,omitempty"` } diff --git a/api/v1alpha1/operandrequest_types.go b/api/v1alpha1/operandrequest_types.go index 19956199..0ddc839d 100644 --- a/api/v1alpha1/operandrequest_types.go +++ b/api/v1alpha1/operandrequest_types.go @@ -29,6 +29,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + "k8s.io/klog" "sigs.k8s.io/controller-runtime/pkg/reconcile" ) @@ -62,7 +63,7 @@ type Operand struct { Name string `json:"name"` // The bindings section is used to specify names of secret and/or configmap. // +optional - Bindings map[string]SecretConfigmap `json:"bindings,omitempty"` + Bindings map[string]Bindable `json:"bindings,omitempty"` // Kind is used when users want to deploy multiple custom resources. // Kind identifies the kind of the custom resource. // +optional @@ -106,6 +107,7 @@ const ( ConditionNotFound ConditionType = "NotFound" ConditionOutofScope ConditionType = "OutofScope" ConditionReady ConditionType = "Ready" + ConditionNoConflict ConditionType = "NoConflict" OperatorReady OperatorPhase = "Ready for Deployment" OperatorRunning OperatorPhase = "Running" @@ -129,6 +131,8 @@ const ( ResourceTypeCsv ResourceType = "csv" ResourceTypeOperator ResourceType = "operator" ResourceTypeOperand ResourceType = "operands" + ResourceTypeConfigmap ResourceType = "configmap" + ResourceTypeDeployment ResourceType = "deployment" ) // Condition represents the current state of the Request Service. @@ -167,11 +171,11 @@ type OperandStatus struct { //Top level CR status ie the CR created by ODLM APIVersion string `json:"apiVersion,omitempty"` Namespace string `json:"namespace,omitempty"` Kind string `json:"kind,omitempty"` - // Type string `json:"type,omitempty"` - Status string `json:"status,omitempty"` - // LastTransitionTime string `json:"lastTransitionTime,omitempty"` //might need to change the variable type + Status string `json:"status,omitempty"` // Message string `json:"message,omitempty"` ManagedResources []ResourceStatus `json:"managedResources,omitempty"` + // Type string `json:"type,omitempty"` + // LastTransitionTime string `json:"lastTransitionTime,omitempty"` //might need to change the variable type } type ServiceStatus struct { //Top level service status @@ -243,13 +247,14 @@ type MemberStatus struct { // +kubebuilder:printcolumn:name="Created At",type=string,JSONPath=.metadata.creationTimestamp // +operator-sdk:csv:customresourcedefinitions:displayName="OperandRequest" -// OperandRequest is the Schema for the operandrequests API. +// OperandRequest is the Schema for the operandrequests API. Documentation For additional details regarding install parameters check https://ibm.biz/icpfs39install. License By installing this product you accept the license terms https://ibm.biz/icpfs39license type OperandRequest struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` // +kubebuilder:pruning:PreserveUnknownFields - Spec OperandRequestSpec `json:"spec,omitempty"` + Spec OperandRequestSpec `json:"spec,omitempty"` + // +kubebuilder:pruning:PreserveUnknownFields Status OperandRequestStatus `json:"status,omitempty"` } @@ -318,6 +323,14 @@ func (r *OperandRequest) SetNotFoundOperandRegistryCondition(name string, rt Res r.setCondition(*c) } +// SetNoConflictOperatorCondition creates a NoConflictCondition when an operator channel does not conflict with others. +func (r *OperandRequest) SetNoConflictOperatorCondition(name string, rt ResourceType, cs corev1.ConditionStatus, mu sync.Locker) { + mu.Lock() + defer mu.Unlock() + c := newCondition(ConditionNoConflict, cs, "No channel conflict on "+string(rt), "No channel conflict on "+string(rt)+" "+name+" in the scope") + r.setCondition(*c) +} + // setReadyCondition creates a Condition to claim Ready. func (r *OperandRequest) setReadyCondition(name string, rt ResourceType, cs corev1.ConditionStatus) { c := &Condition{} @@ -409,6 +422,24 @@ func (r *OperandRequest) RemoveMemberCRStatus(name, CRName, CRKind string, mu sy } } +func (r *OperandRequest) RemoveServiceStatus(operatorName string, mu sync.Locker) { + mu.Lock() + defer mu.Unlock() + pos, s := getServiceStatus(&r.Status, operatorName) + if s != nil { + r.Status.Services = append(r.Status.Services[:pos], r.Status.Services[pos+1:]...) + } +} + +func (r *OperandRequest) RemoveOperandPhase(name string, mu sync.Locker) { + mu.Lock() + defer mu.Unlock() + pos, m := getMemberStatus(&r.Status, name) + if m != nil { + r.Status.Members[pos].Phase = MemberPhase{} + } +} + func (r *OperandRequest) SetServiceStatus(ctx context.Context, service ServiceStatus, updater client.StatusClient, mu sync.Locker) error { mu.Lock() defer mu.Unlock() @@ -474,10 +505,10 @@ func (r *OperandRequest) setOperandReadyCondition(operandPhase ServicePhase, nam } // FreshMemberStatus cleanup Member status from the Member status list. -func (r *OperandRequest) FreshMemberStatus(failedDeletedOperands *gset.Set) { +func (r *OperandRequest) FreshMemberStatus(remainingOp *gset.Set) { newMembers := []MemberStatus{} for index, m := range r.Status.Members { - if foundOperand(r.Spec.Requests, m.Name) || (*failedDeletedOperands).Contains(m.Name) { + if foundOperand(r.Spec.Requests, m.Name) || (*remainingOp).Contains(m.Name) { newMembers = append(newMembers, r.Status.Members[index]) } } @@ -602,12 +633,16 @@ func (r *OperandRequest) GetRegistryKey(req Request) types.NamespacedName { } // InitRequestStatus OperandConfig status. -func (r *OperandRequest) InitRequestStatus() bool { +func (r *OperandRequest) InitRequestStatus(mu sync.Locker) bool { isInitialized := true if r.Status.Phase == "" { isInitialized = false - r.Status.Phase = ClusterPhaseNone } + for _, member := range r.Status.Members { + klog.V(2).Info("Cleaning the member status for Operand: ", member.Name) + r.RemoveOperandPhase(member.Name, mu) + } + r.Status.Phase = ClusterPhaseNone return isInitialized } diff --git a/api/v1alpha1/operatorconfig_types.go b/api/v1alpha1/operatorconfig_types.go new file mode 100644 index 00000000..e2661d7d --- /dev/null +++ b/api/v1alpha1/operatorconfig_types.go @@ -0,0 +1,106 @@ +// +// Copyright 2022 IBM Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package v1alpha1 + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// OperatorConfigSpec defines the desired state of OperatorConfig +// +kubebuilder:pruning:PreserveUnknownFields +type OperatorConfigSpec 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 OperatorConfig. Edit operatorconfig_types.go to remove/update + Foo string `json:"foo,omitempty"` + + // Services is a list of services to be configured, specifically their operators + // +kubebuilder:pruning:PreserveUnknownFields + Services []ServiceOperatorConfig `json:"services,omitempty"` +} + +// ServiceOperatorConfig defines the configuration of the service. +type ServiceOperatorConfig struct { + // Name is the operator name as requested in the OperandRequest. + Name string `json:"name"` + // If specified, the pod's scheduling constraints + // +optional + Affinity *corev1.Affinity `json:"affinity,omitempty" protobuf:"bytes,18,opt,name=affinity"` + // Number of desired pods. This is a pointer to distinguish between explicit + // zero and not specified. Defaults to 1. + // +optional + Replicas *int32 `json:"replicas,omitempty" protobuf:"varint,1,opt,name=replicas"` + // TopologySpreadConstraints describes how a group of pods ought to spread across topology + // domains. Scheduler will schedule pods in a way which abides by the constraints. + // All topologySpreadConstraints are ANDed. + // +optional + // +patchMergeKey=topologyKey + // +patchStrategy=merge + // +listType=map + // +listMapKey=topologyKey + // +listMapKey=whenUnsatisfiable + TopologySpreadConstraints []corev1.TopologySpreadConstraint `json:"topologySpreadConstraints,omitempty" patchStrategy:"merge" patchMergeKey:"topologyKey" protobuf:"bytes,33,opt,name=topologySpreadConstraints"` +} + +// OperatorConfigStatus defines the observed state of OperatorConfig +// +kubebuilder:pruning:PreserveUnknownFields +type OperatorConfigStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +// +kubebuilder:resource:path=operatorconfigs,scope=Namespaced +// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=.metadata.creationTimestamp +// +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=.status.phase,description="Current Phase" +// +kubebuilder:printcolumn:name="Created At",type=string,JSONPath=.metadata.creationTimestamp +// +operator-sdk:csv:customresourcedefinitions:displayName="OperatorConfig" + +// OperatorConfig is the Schema for the operatorconfigs API. Documentation For additional details regarding install parameters check https://ibm.biz/icpfs39install. License By installing this product you accept the license terms https://ibm.biz/icpfs39license +type OperatorConfig struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec OperatorConfigSpec `json:"spec,omitempty"` + Status OperatorConfigStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// OperatorConfigList contains a list of OperatorConfig +type OperatorConfigList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []OperatorConfig `json:"items"` +} + +// GetConfigForOperator obtains a particular ServiceOperatorConfig by using operator name for searching. +func (r *OperatorConfig) GetConfigForOperator(name string) *ServiceOperatorConfig { + for _, o := range r.Spec.Services { + if o.Name == name { + return &o + } + } + return nil +} + +func init() { + SchemeBuilder.Register(&OperatorConfig{}, &OperatorConfigList{}) +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index ad04906b..d8720dec 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -1,5 +1,4 @@ //go:build !ignore_autogenerated -// +build !ignore_autogenerated // // Copyright 2022 IBM Corporation @@ -23,9 +22,35 @@ package v1alpha1 import ( operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" + "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Bindable) DeepCopyInto(out *Bindable) { + *out = *in + if in.Route != nil { + in, out := &in.Route, &out.Route + *out = new(Route) + (*in).DeepCopyInto(*out) + } + if in.Service != nil { + in, out := &in.Service, &out.Service + *out = new(ServiceData) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Bindable. +func (in *Bindable) DeepCopy() *Bindable { + if in == nil { + return nil + } + out := new(Bindable) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Condition) DeepCopyInto(out *Condition) { *out = *in @@ -63,6 +88,13 @@ func (in *ConfigResource) DeepCopyInto(out *ConfigResource) { *out = new(runtime.RawExtension) (*in).DeepCopyInto(*out) } + if in.OwnerReferences != nil { + in, out := &in.OwnerReferences, &out.OwnerReferences + *out = make([]OwnerReference, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigResource. @@ -80,7 +112,7 @@ func (in *ConfigService) DeepCopyInto(out *ConfigService) { *out = *in if in.Spec != nil { in, out := &in.Spec, &out.Spec - *out = make(map[string]runtime.RawExtension, len(*in)) + *out = make(map[string]ExtensionWithMarker, len(*in)) for key, val := range *in { (*out)[key] = *val.DeepCopy() } @@ -126,6 +158,22 @@ func (in *CrStatus) DeepCopy() *CrStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExtensionWithMarker) DeepCopyInto(out *ExtensionWithMarker) { + *out = *in + in.RawExtension.DeepCopyInto(&out.RawExtension) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtensionWithMarker. +func (in *ExtensionWithMarker) DeepCopy() *ExtensionWithMarker { + if in == nil { + return nil + } + out := new(ExtensionWithMarker) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *MemberPhase) DeepCopyInto(out *MemberPhase) { *out = *in @@ -167,9 +215,9 @@ func (in *Operand) DeepCopyInto(out *Operand) { *out = *in if in.Bindings != nil { in, out := &in.Bindings, &out.Bindings - *out = make(map[string]SecretConfigmap, len(*in)) + *out = make(map[string]Bindable, len(*in)) for key, val := range *in { - (*out)[key] = val + (*out)[key] = *val.DeepCopy() } } if in.Spec != nil { @@ -253,9 +301,9 @@ func (in *OperandBindInfoSpec) DeepCopyInto(out *OperandBindInfoSpec) { *out = *in if in.Bindings != nil { in, out := &in.Bindings, &out.Bindings - *out = make(map[string]SecretConfigmap, len(*in)) + *out = make(map[string]Bindable, len(*in)) for key, val := range *in { - (*out)[key] = val + (*out)[key] = *val.DeepCopy() } } } @@ -612,6 +660,13 @@ func (in *OperandRequestStatus) DeepCopyInto(out *OperandRequestStatus) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.Services != nil { + in, out := &in.Services, &out.Services + *out = make([]ServiceStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OperandRequestStatus. @@ -624,6 +679,26 @@ func (in *OperandRequestStatus) DeepCopy() *OperandRequestStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OperandStatus) DeepCopyInto(out *OperandStatus) { + *out = *in + if in.ManagedResources != nil { + in, out := &in.ManagedResources, &out.ManagedResources + *out = make([]ResourceStatus, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OperandStatus. +func (in *OperandStatus) DeepCopy() *OperandStatus { + if in == nil { + return nil + } + out := new(OperandStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Operator) DeepCopyInto(out *Operator) { *out = *in @@ -649,6 +724,102 @@ func (in *Operator) DeepCopy() *Operator { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OperatorConfig) DeepCopyInto(out *OperatorConfig) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OperatorConfig. +func (in *OperatorConfig) DeepCopy() *OperatorConfig { + if in == nil { + return nil + } + out := new(OperatorConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *OperatorConfig) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OperatorConfigList) DeepCopyInto(out *OperatorConfigList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]OperatorConfig, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OperatorConfigList. +func (in *OperatorConfigList) DeepCopy() *OperatorConfigList { + if in == nil { + return nil + } + out := new(OperatorConfigList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *OperatorConfigList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OperatorConfigSpec) DeepCopyInto(out *OperatorConfigSpec) { + *out = *in + if in.Services != nil { + in, out := &in.Services, &out.Services + *out = make([]ServiceOperatorConfig, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OperatorConfigSpec. +func (in *OperatorConfigSpec) DeepCopy() *OperatorConfigSpec { + if in == nil { + return nil + } + out := new(OperatorConfigSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OperatorConfigStatus) DeepCopyInto(out *OperatorConfigStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OperatorConfigStatus. +func (in *OperatorConfigStatus) DeepCopy() *OperatorConfigStatus { + if in == nil { + return nil + } + out := new(OperatorConfigStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OperatorStatus) DeepCopyInto(out *OperatorStatus) { *out = *in @@ -669,6 +840,31 @@ func (in *OperatorStatus) DeepCopy() *OperatorStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OwnerReference) DeepCopyInto(out *OwnerReference) { + *out = *in + if in.Controller != nil { + in, out := &in.Controller, &out.Controller + *out = new(bool) + **out = **in + } + if in.BlockOwnerDeletion != nil { + in, out := &in.BlockOwnerDeletion, &out.BlockOwnerDeletion + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OwnerReference. +func (in *OwnerReference) DeepCopy() *OwnerReference { + if in == nil { + return nil + } + out := new(OwnerReference) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ReconcileRequest) DeepCopyInto(out *ReconcileRequest) { *out = *in @@ -707,16 +903,114 @@ func (in *Request) DeepCopy() *Request { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SecretConfigmap) DeepCopyInto(out *SecretConfigmap) { +func (in *ResourceStatus) DeepCopyInto(out *ResourceStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceStatus. +func (in *ResourceStatus) DeepCopy() *ResourceStatus { + if in == nil { + return nil + } + out := new(ResourceStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Route) DeepCopyInto(out *Route) { + *out = *in + if in.Data != nil { + in, out := &in.Data, &out.Data + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Route. +func (in *Route) DeepCopy() *Route { + if in == nil { + return nil + } + out := new(Route) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServiceData) DeepCopyInto(out *ServiceData) { *out = *in + if in.Data != nil { + in, out := &in.Data, &out.Data + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceData. +func (in *ServiceData) DeepCopy() *ServiceData { + if in == nil { + return nil + } + out := new(ServiceData) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServiceOperatorConfig) DeepCopyInto(out *ServiceOperatorConfig) { + *out = *in + if in.Affinity != nil { + in, out := &in.Affinity, &out.Affinity + *out = new(v1.Affinity) + (*in).DeepCopyInto(*out) + } + if in.Replicas != nil { + in, out := &in.Replicas, &out.Replicas + *out = new(int32) + **out = **in + } + if in.TopologySpreadConstraints != nil { + in, out := &in.TopologySpreadConstraints, &out.TopologySpreadConstraints + *out = make([]v1.TopologySpreadConstraint, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceOperatorConfig. +func (in *ServiceOperatorConfig) DeepCopy() *ServiceOperatorConfig { + if in == nil { + return nil + } + out := new(ServiceOperatorConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServiceStatus) DeepCopyInto(out *ServiceStatus) { + *out = *in + if in.Resources != nil { + in, out := &in.Resources, &out.Resources + *out = make([]OperandStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretConfigmap. -func (in *SecretConfigmap) DeepCopy() *SecretConfigmap { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceStatus. +func (in *ServiceStatus) DeepCopy() *ServiceStatus { if in == nil { return nil } - out := new(SecretConfigmap) + out := new(ServiceStatus) in.DeepCopyInto(out) return out } diff --git a/base_images.json b/base_images.json index 4006b59d..ec22d1ca 100644 --- a/base_images.json +++ b/base_images.json @@ -1,57 +1,49 @@ [ - { - "imageType": "external", - "sourceRepo": "registry.access.redhat.com", - "sourceNamespace": "ubi8", - "sourceImage": "ubi", - "destStage": "edge", - "destNamespace": "build-images", - "destImage": "ubi8", - "tag": "8.6-990", - "updatePackages": [] - }, - { - "imageType": "external", - "sourceRepo": "registry.access.redhat.com", - "sourceNamespace": "ubi8", - "sourceImage": "ubi-minimal", - "destStage": "edge", - "destNamespace": "build-images", - "destImage": "ubi8-minimal", - "tag": "8.6-994", - "updatePackages": [] - }, - { - "imageType": "external", - "sourceRepo": "quay.io", - "sourceNamespace": "operator-framework", - "sourceImage": "helm-operator", - "destStage": "edge", - "destNamespace": "build-images", - "destImage": "helm-operator", - "tag": "v1.25.1", - "updatePackages": [] - }, - { - "imageType": "node", - "sourceImage": "ubi8-minimal", - "sourceTag": "8.6-994", - "destImage": "node-fermium-ubi8-minimal", - "nodeVersion": "14.21.1" - }, - { - "imageType": "node", - "sourceImage": "ubi8-minimal", - "sourceTag": "8.6-994", - "destImage": "node-fermium-npm8-ubi8-minimal", - "nodeVersion": "14.21.1", - "npmVersion": "8" - }, - { - "imageType": "node", - "sourceImage": "ubi8-minimal", - "sourceTag": "8.6-994", - "destImage": "node-v16-ubi8-minimal", - "nodeVersion": "16.18.1" - } + { + "imageType": "external", + "sourceRepo": "registry.access.redhat.com", + "sourceNamespace": "ubi9", + "sourceImage": "ubi-minimal", + "destStage": "edge", + "destNamespace": "build-images", + "destImage": "ubi9-minimal", + "tag": "9.5-1738643652", + "updatePackages": [] + }, + { + "imageType": "external", + "sourceRepo": "registry.access.redhat.com", + "sourceNamespace": "ubi9", + "sourceImage": "ubi", + "destStage": "edge", + "destNamespace": "build-images", + "destImage": "ubi9", + "tag": "9.5-1738643550", + "updatePackages": [] + }, + { + "imageType": "external", + "sourceRepo": "registry.access.redhat.com", + "sourceNamespace": "ubi9", + "sourceImage": "ubi-micro", + "destStage": "edge", + "destNamespace": "build-images", + "destImage": "ubi9-micro", + "tag": "9.5-1738659858", + "updatePackages": [] + }, + { + "imageType": "node", + "sourceImage": "ubi9-minimal", + "sourceTag": "9.5-1738643652", + "destImage": "node-v20-ubi9-minimal", + "nodeVersion": "20.18.2" + }, + { + "imageType": "node", + "sourceImage": "ubi9-minimal", + "sourceTag": "9.5-1738643652", + "destImage": "node-v22-ubi9-minimal", + "nodeVersion": "22.13.1" + } ] diff --git a/bundle.Dockerfile b/bundle.Dockerfile index 253be295..162e5db1 100644 --- a/bundle.Dockerfile +++ b/bundle.Dockerfile @@ -5,9 +5,9 @@ LABEL operators.operatorframework.io.bundle.mediatype.v1=registry+v1 LABEL operators.operatorframework.io.bundle.manifests.v1=manifests/ LABEL operators.operatorframework.io.bundle.metadata.v1=metadata/ LABEL operators.operatorframework.io.bundle.package.v1=ibm-odlm -LABEL operators.operatorframework.io.bundle.channels.v1=v4.0 -LABEL operators.operatorframework.io.bundle.channel.default.v1=v4.0 -LABEL operators.operatorframework.io.metrics.builder=operator-sdk-v1.24.0 +LABEL operators.operatorframework.io.bundle.channels.v1=v4.3 +LABEL operators.operatorframework.io.bundle.channel.default.v1=v4.3 +LABEL operators.operatorframework.io.metrics.builder=operator-sdk-v1.32.0 LABEL operators.operatorframework.io.metrics.mediatype.v1=metrics+v1 LABEL operators.operatorframework.io.metrics.project_layout=go.kubebuilder.io/v3 diff --git a/bundle/manifests/operand-deployment-lifecycle-manager.clusterserviceversion.yaml b/bundle/manifests/operand-deployment-lifecycle-manager.clusterserviceversion.yaml index 6f56ddd7..8d45defa 100644 --- a/bundle/manifests/operand-deployment-lifecycle-manager.clusterserviceversion.yaml +++ b/bundle/manifests/operand-deployment-lifecycle-manager.clusterserviceversion.yaml @@ -4,6 +4,29 @@ metadata: annotations: alm-examples: |- [ + { + "apiVersion": "operator.ibm.com/v1alpha1", + "kind": "OperandBindInfo", + "metadata": { + "labels": { + "app.kubernetes.io/instance": "operand-deployment-lifecycle-manager", + "app.kubernetes.io/managed-by": "operand-deployment-lifecycle-manager", + "app.kubernetes.io/name": "operand-deployment-lifecycle-manager" + }, + "name": "example-service" + }, + "spec": { + "bindings": { + "public": { + "configmap": "mongodb-configmap", + "secret": "mongodb-secret" + } + }, + "description": "Binding information that should be accessible to MongoDB adopters", + "operand": "mongodb-atlas-kubernetes", + "registry": "example-service" + } + }, { "apiVersion": "operator.ibm.com/v1alpha1", "kind": "OperandConfig", @@ -18,19 +41,22 @@ metadata: "spec": { "services": [ { - "name": "etcd", + "name": "jaeger", "spec": { - "etcdCluster": { - "size": 1 + "jaeger": { + "strategy": "allinone" } } }, { - "name": "jenkins", + "name": "mongodb-atlas-kubernetes", "spec": { - "jenkins": { - "service": { - "port": 8081 + "atlasDeployment": { + "deploymentSpec": { + "name": "test-deployment" + }, + "projectRef": { + "name": "my-project" } } } @@ -52,19 +78,19 @@ metadata: "spec": { "operators": [ { - "channel": "clusterwide-alpha", + "channel": "stable", "installMode": "cluster", - "name": "etcd", - "namespace": "etcd-cluster-operator", - "packageName": "etcd", + "name": "jaeger", + "namespace": "default", + "packageName": "jaeger", "sourceName": "community-operators", "sourceNamespace": "openshift-marketplace" }, { - "channel": "alpha", - "name": "jenkins", + "channel": "stable", + "name": "mongodb-atlas-kubernetes", "namespace": "default", - "packageName": "jenkins-operator", + "packageName": "mongodb-atlas-kubernetes", "sourceName": "community-operators", "sourceNamespace": "openshift-marketplace" } @@ -87,10 +113,10 @@ metadata: { "operands": [ { - "name": "etcd" + "name": "jaeger" }, { - "name": "jenkins" + "name": "mongodb-atlas-kubernetes" } ], "registry": "example-service" @@ -103,91 +129,104 @@ metadata: categories: Developer Tools, Monitoring, Logging & Tracing, Security certified: "false" containerImage: icr.io/cpopen/odlm:latest - createdAt: "2020-11-12T17:05:48Z" - description: The Operand Deployment Lifecycle Manager provides a Kubernetes CRD-based - API to manage the lifecycle of operands. - olm.skipRange: '>=1.2.0 <4.0.0' - operators.operatorframework.io/builder: operator-sdk-v1.24.0 + createdAt: "2024-09-28T19:55:35Z" + description: The Operand Deployment Lifecycle Manager provides a Kubernetes CRD-based API to manage the lifecycle of operands. + nss.operator.ibm.com/managed-operators: ibm-odlm + olm.skipRange: '>=1.2.0 <4.4.0' + operators.openshift.io/infrastructure-features: '["disconnected"]' + operators.operatorframework.io/builder: operator-sdk-v1.32.0 operators.operatorframework.io/project_layout: go.kubebuilder.io/v3 repository: https://github.com/IBM/operand-deployment-lifecycle-manager support: IBM + features.operators.openshift.io/disconnected: "true" + features.operators.openshift.io/fips-compliant: "true" + features.operators.openshift.io/proxy-aware: "false" + features.operators.openshift.io/tls-profiles: "false" + features.operators.openshift.io/token-auth-aws: "false" + features.operators.openshift.io/token-auth-azure: "false" + features.operators.openshift.io/token-auth-gcp: "false" labels: operatorframework.io/arch.amd64: supported operatorframework.io/arch.ppc64le: supported operatorframework.io/arch.s390x: supported operatorframework.io/os.linux: supported - name: operand-deployment-lifecycle-manager.v4.0.0 + name: operand-deployment-lifecycle-manager.v4.4.0 namespace: placeholder spec: apiservicedefinitions: {} customresourcedefinitions: owned: - - description: OperandBindInfo is the Schema for the operandbindinfoes API. Documentation For additional details regarding install parameters check https://ibm.biz/icpfs39install. License By installing this product you accept the license terms https://ibm.biz/icpfs39license - displayName: OperandBindInfo - kind: OperandBindInfo - name: operandbindinfos.operator.ibm.com - statusDescriptors: - - description: Phase describes the overall phase of OperandBindInfo. - displayName: Phase - path: phase - x-descriptors: - - urn:alm:descriptor:io.kubernetes.phase - version: v1alpha1 - - description: OperandConfig is the Schema for the operandconfigs API. Documentation For additional details regarding install parameters check https://ibm.biz/icpfs39install. License By installing this product you accept the license terms https://ibm.biz/icpfs39license - displayName: OperandConfig - kind: OperandConfig - name: operandconfigs.operator.ibm.com - specDescriptors: - - description: Services is a list of configuration of service. - displayName: Operand Services Config List - path: services - statusDescriptors: - - description: Phase describes the overall phase of operands in the OperandConfig. - displayName: Phase - path: phase - x-descriptors: - - urn:alm:descriptor:io.kubernetes.phase - version: v1alpha1 - - description: OperandRegistry is the Schema for the operandregistries API. Documentation For additional details regarding install parameters check https://ibm.biz/icpfs39install. License By installing this product you accept the license terms https://ibm.biz/icpfs39license - displayName: OperandRegistry - kind: OperandRegistry - name: operandregistries.operator.ibm.com - specDescriptors: - - description: Operators is a list of operator OLM definition. - displayName: Operators Registry List - path: operators - statusDescriptors: - - description: Conditions represents the current state of the Request Service. - displayName: Conditions - path: conditions - x-descriptors: - - urn:alm:descriptor:io.kubernetes.conditions - - description: Phase describes the overall phase of operators in the OperandRegistry. - displayName: Phase - path: phase - x-descriptors: - - urn:alm:descriptor:io.kubernetes.phase - version: v1alpha1 - - description: OperandRequest is the Schema for the operandrequests API. Documentation For additional details regarding install parameters check https://ibm.biz/icpfs39install. License By installing this product you accept the license terms https://ibm.biz/icpfs39license - displayName: OperandRequest - kind: OperandRequest - name: operandrequests.operator.ibm.com - specDescriptors: - - description: Requests defines a list of operands installation. - displayName: Operators Request List - path: requests - statusDescriptors: - - description: Conditions represents the current state of the Request Service. - displayName: Conditions - path: conditions - x-descriptors: - - urn:alm:descriptor:io.kubernetes.conditions - - description: Phase is the cluster running phase. - displayName: Phase - path: phase - x-descriptors: - - urn:alm:descriptor:io.kubernetes.phase - version: v1alpha1 + - description: OperandBindInfo is the Schema for the operandbindinfoes API. Documentation For additional details regarding install parameters check https://ibm.biz/icpfs39install. License By installing this product you accept the license terms https://ibm.biz/icpfs39license + displayName: OperandBindInfo + kind: OperandBindInfo + name: operandbindinfos.operator.ibm.com + statusDescriptors: + - description: Phase describes the overall phase of OperandBindInfo. + displayName: Phase + path: phase + x-descriptors: + - urn:alm:descriptor:io.kubernetes.phase + version: v1alpha1 + - description: OperandConfig is the Schema for the operandconfigs API. Documentation For additional details regarding install parameters check https://ibm.biz/icpfs39install. License By installing this product you accept the license terms https://ibm.biz/icpfs39license + displayName: OperandConfig + kind: OperandConfig + name: operandconfigs.operator.ibm.com + specDescriptors: + - description: Services is a list of configuration of service. + displayName: Operand Services Config List + path: services + statusDescriptors: + - description: Phase describes the overall phase of operands in the OperandConfig. + displayName: Phase + path: phase + x-descriptors: + - urn:alm:descriptor:io.kubernetes.phase + version: v1alpha1 + - description: OperandRegistry is the Schema for the operandregistries API. Documentation For additional details regarding install parameters check https://ibm.biz/icpfs39install. License By installing this product you accept the license terms https://ibm.biz/icpfs39license + displayName: OperandRegistry + kind: OperandRegistry + name: operandregistries.operator.ibm.com + specDescriptors: + - description: Operators is a list of operator OLM definition. + displayName: Operators Registry List + path: operators + statusDescriptors: + - description: Conditions represents the current state of the Request Service. + displayName: Conditions + path: conditions + x-descriptors: + - urn:alm:descriptor:io.kubernetes.conditions + - description: Phase describes the overall phase of operators in the OperandRegistry. + displayName: Phase + path: phase + x-descriptors: + - urn:alm:descriptor:io.kubernetes.phase + version: v1alpha1 + - description: OperandRequest is the Schema for the operandrequests API. Documentation For additional details regarding install parameters check https://ibm.biz/icpfs39install. License By installing this product you accept the license terms https://ibm.biz/icpfs39license + displayName: OperandRequest + kind: OperandRequest + name: operandrequests.operator.ibm.com + specDescriptors: + - description: Requests defines a list of operands installation. + displayName: Operators Request List + path: requests + statusDescriptors: + - description: Conditions represents the current state of the Request Service. + displayName: Conditions + path: conditions + x-descriptors: + - urn:alm:descriptor:io.kubernetes.conditions + - description: Phase is the cluster running phase. + displayName: Phase + path: phase + x-descriptors: + - urn:alm:descriptor:io.kubernetes.phase + version: v1alpha1 + - description: OperatorConfig is the Schema for the operatorconfigs API. Documentation For additional details regarding install parameters check https://ibm.biz/icpfs39install. License By installing this product you accept the license terms https://ibm.biz/icpfs39license + displayName: OperatorConfig + kind: OperatorConfig + name: operatorconfigs.operator.ibm.com + version: v1alpha1 description: |- # Introduction @@ -530,139 +569,313 @@ spec: The Operand Deployment Lifecycle Manager supports running under the OpenShift Container Platform default restricted security context constraints. displayName: Operand Deployment Lifecycle Manager icon: - - base64data: iVBORw0KGgoAAAANSUhEUgAAAK8AAACvCAMAAAC8TH5HAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAB1UExURQAAAJGS77CC4pCS75yM64uV8pSQ7puA85OV87OB4auF5Hyd+H2c936b9n6b94Ca9n+b9n+b9n+b9qOJ56SI55yM6qSI536b96aH5q2D45mN64OZ9ZWQ7oyU8XWg+6uG5oqg/p6L6m+k/ZuY+3mr/6qQ9LqM80D8C0oAAAAbdFJOUwA67R4KKxMBBP6ak6vZgVtJxG5ot+hQ7YDVkwC2C58AAAuSSURBVHja7ZyJerK8EoCDCSTKjoiIS13of/+XeGYm4NLKrvj1OYxt7aa8TiazJZGxSSaZZJJJJvmcSCn/Eq7Cz79DLJk0rb+kXdM9nz0m/4p2mZufz3lAZvEn1HsGye2J9128h7/Gezj8Nd7D3+I9/xu8SjWHrS76bfN8A+NsYxjowCvbPN+QSGB6kWi6QHteyQLPfx+wYsH2eHSthgu05lXMy/PceRcwxtnjdnts4mjLq5hBceVdcVsya71FMeov0JIXMuQwR+DoXX5EMgf0uz2GrDYbb8mrmE+4Z/NdvDCApN+jX3uFdrySqfW70wzFbFLwWtVNkXa8ONlIvfx9Dk0xSyvYq0NpxasYJ9o8emcUVCw6EjGvuUpLXgfVm9cP1fAZp1yyCKeGBf8pB96g9jUZ57c6s1vIIAUfjXqY9eFg1yiuKJnOECzeW+TJm0+rxRGGWfcP7/dld8bZwqcp/dJqIs9hrJIJ/JD2abV5j1StfJn1/pofo/Kx0ae1KfAO7/Vld7anfVpf28M5kKPDc9kYLRW4RDhIwYV/PozVUAF39Qre3BmrvsM04nisjHHyJlUjZEOefuBj8UIA81zHfGJ84BYeHAP9LKseP1r5LNnvOlHeXJgqRZbUPzT97PHvBVb48VCX09W54du2u3ZJwjD0It/gqmCue/yoolm4b7tQjmohh7cGAWzHC8x/qOFOZmBG4bbERDkQrVYyiGP7iPwPLGrgsAofYbePonEJ2CHxAuvjxEjLvfUj7J1BaP0irY3i888SA63l3alWgwKjbXueZztOSBoucOE33huIZdsWHChXRds72O069PyHhSEBDiOynbAEBiGreCGJKoa5zT8GVBzt4QNgXc+wbq4YvW+hSMkDYNa4EYihWqlYtmouSsYTo4XvgWezHKDcI+7xuPbMMp7JH0GEfhZGRMDIG5FRtLG1IGCNvTp/d9nFZhMx/DXYH/cgSBv6SscM+Tyf0P450Lw+iCmbOGAMonOeO/XlMyTjgAsfmWAN9Y53RFy0hDAovXBDSBFBVAIHDdUJ2lre3J6AVG9Hcln5NQyKCUcrd390g5/BtjpNR2KNGwTVpRDSmk6et6jwCv0ScVhpxopxl3DBIjzVjrYk5gVuEPAaw7UP+aFV+0ex5Aq8y/hTYhiE/UXjhibrlBUisUm8hmHwqujuH3IqQLA/0dT+Af8Q34hT8du3QXlR4nrdkxhJ0554nwAXhpvj+hLUo2u/zWoJM1aXy70ZP8e97APWJ+WGbN1AXNP8tedAasM96PLu4Ik2jhpHZLkqgdGM5TNjuKzNnhkiUmneH8CSCe9wpXV429HDlCu7GcV9JwemWoEbWr3rGZx2iMs5F4+T3S1p89DoYGvkUeLCKC67m+uBsVwVuGpI+QVohGtZ6rHrU+Cu/UaP/ps4KY3iWhlipwNwd4Arh1WLCIy4lpA/2yiF4XZ9ehgMuaRgt7r6FMWiC9DuL64YWtyCrQKuEOLe1iJsG+eO2W8eo+POdrvVtdULrgG0Dbg76xW1uCDcm5GCguzDAeNlz0qPqgfzGunJeAl4aOug6KYQ7l2WhI7DZEMqZ7L5a1uBZWTQF3/QVHvmUosOBX0ZVkbfkgNtDYCbDcDVsIKbQYCJBCY/gak7FHQh+bqiX7LwsnuYfr1gqUTCUsPWgsWdF1H2I1/ZoYBMSLs3o3/blyke+FRiEPE9c1Huq9dpV60GWQNmvybSIrCnee0SGIlDJzJfVzwrttTq7bfkUNCSzV71a19pScNOGHrmi9pWV/Uue6lXYpEcBFfgslSOPG0MBTASc/YK3455PEqvyYY5r0G4AeH6gWHqSCyVxQ2s9ksJw9B/ATBYVUy8fdRL6ZhhlPo1HpIyHelM38OmCuA6oWvzwTah69DTbiW6qxdMCdPdAIGLbrC8lyIimxHRgrhQcA+cdoqluxXc0u7qhcTGNBAYeKkB9CTASfJjVuTo7mvoRsO676Ci+LRanVbd91YgLggp2GI1/kpRq7MAXnuDjBhC8Qpkl3UepwIXgblseDQq2XBcUK8bru0hGgbni7ynzrMNs1xOuJDmNQMAsfAI2B0CjOaAvKuuK2aES8C8XU8Sn98H9SKw12/SwfwVzNyArOLOL1lxEpO37/lKFujlpW3UfTSZwpxaQCkXb+JVd3OAAg1xrQ4vFGzC0MDrbuvLSGtRiSVYuonjeNU5MxMWAVudZzct1azdLmUXzGZLV7BCySxG6Zrq4MsFXqv79A7WiLu1OwwLFgElr7VA3LQjLtZnCCx7+KNo7a4BuG3lhRmKWXQ0LME40Gbxsqt6BQH3arExZ+viCl67Ib1rGHFLQPIQL7JFnHTjRfUCb68whR1mXM3dttpjcWvIAS6uNCRxlmVxxypeCVJw3wjl0/LzmrfaVG4kBgFT6ge57wJ4M7OTfmlNS4j+McpB4G2rTfBGkhAwp2UcWfB2cw/FFogBKQvxrhtTLMnMZYJiFG4eeLM0zVLRg3dIzmJvAbfRgiXjS81rXfeBLIE3TTuVQneZeH8Fb4HXFQ0rcGKJcsNFXsRdduYdViSQBQNy0LCilaSIu+R3TeqP8KKLQAXXzjgw3hR5l3erFvoldOOVr9Cv5eK6v1tzXch0UZfLNGEPvGQi3fU7tMi1m45PgCtb4Nin974Lftmd9yUtJZ94q/NgUG9KvA9rWOjgwKATMTqv3mpcbcDgQxaLRbpYyp+89/5tLMF98GTAVZsP4LfpAuXRYnALBwof+0AxejR0EVVpO4ARbvpz96D1GV7FvNoJB4lNDLiQOKofIQSTicQcnzeq5ZUsxTpi8ctQJeVrJmNj8wbEWxHhYNxjXff8UiT1vww1Oq9R59Dgz1gGb5Kff5a62jA/4tD222Ml75J4zd+8uglmfcQB76s2nktsM2w2z8p2yamWG90eTNrd9ly/ALnAtlP8LO5a1FdSo9sv7h3cVvGqGHkXT9Sr+3ZcjO4faNNYUMErkHf2tIeuqBNhjc0bHXEDoVHBa20qeRm1liw1Mq9H29z68Ard+hs7f0BzWD/3S8g7q+TV3RohR8VVLqq34pgR2G8NL9O8alx3Rrvy7Cr3q2LkXTyPClrBY55JgPqCthFGVbxsgbxxRd2jxKCGTS/zpelW0beD8pB4NxVhVw7t2HSvj0m9lfUx5A/zzWw2q0yPHzYHjWEOuDXvWLnhAtL1Gah3XrWsImkL/WjAkoX7au+r00bQ7my+qFr4ekETpFvyUGsOKOAgZrNNZaE2InCx9XF/qVmFQwNGBVevs42n31K9+5oqFxw0GURc22UayXjBenHrY1Z7UJ/FpOCkRsFjWe+SNsLuef2xCm0QMfvwe60pxnGf5v7iNTR/xWZWb8GjWcOFgBtK3FLBM+uTCpatd5aigue1Pngs4yVcp8VphmT+YYuQGIhxm/Fu37w+j0mPBk4+BIy4ett8q52lGJTneJsbHwHGwx/FQYp2Q6wtogCWH8DNLtdt0S1Pi6RICx8JG1nFCluOV9yWLgrrjAI4HfVQNtYu5emw9ri0EyZGWpCNORYxvVuAGZeHgLIuEVZB5UnAqGLryfsLvDx31Gfa6czSSW+D7XRFVZgEyizlRfEm3yJFSaiM+HQ5Ee5ll3SNVgCczkvi+SJ5c+PMMtIV0BLu6RL32P8Lry8pcVHJcZoYlniDcCNJ49Xp+/uk5QK20PP0kLWYP8qsg2zuvl/VyAlQS1bQ7SnjfQ814O7WeF4jX/P/5l//fT2V77svePeNd/gFNam/FN/eZPd9io0B/ojOwMWVsA8/wO1RZvc/nOgTbqfi7okAfDbUe+KDjcVsPq9X81eJPK/g/So476kfWUG1S6vjmcIqYpGkGwT7r4t8FfffdIP7ajmdNlnC2Qto2fWNtixjudRr4a+VLF0uTa4vJF8XKuXbg/Hr33TjffKn3gp/kkkmmWSSSSaZZJJJJplkkkkmmWSS/yf5H6HANgUotAMHAAAAAElFTkSuQmCC - mediatype: image/png + - base64data: iVBORw0KGgoAAAANSUhEUgAAAK8AAACvCAMAAAC8TH5HAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAB1UExURQAAAJGS77CC4pCS75yM64uV8pSQ7puA85OV87OB4auF5Hyd+H2c936b9n6b94Ca9n+b9n+b9n+b9qOJ56SI55yM6qSI536b96aH5q2D45mN64OZ9ZWQ7oyU8XWg+6uG5oqg/p6L6m+k/ZuY+3mr/6qQ9LqM80D8C0oAAAAbdFJOUwA67R4KKxMBBP6ak6vZgVtJxG5ot+hQ7YDVkwC2C58AAAuSSURBVHja7ZyJerK8EoCDCSTKjoiIS13of/+XeGYm4NLKrvj1OYxt7aa8TiazJZGxSSaZZJJJJvmcSCn/Eq7Cz79DLJk0rb+kXdM9nz0m/4p2mZufz3lAZvEn1HsGye2J9128h7/Gezj8Nd7D3+I9/xu8SjWHrS76bfN8A+NsYxjowCvbPN+QSGB6kWi6QHteyQLPfx+wYsH2eHSthgu05lXMy/PceRcwxtnjdnts4mjLq5hBceVdcVsya71FMeov0JIXMuQwR+DoXX5EMgf0uz2GrDYbb8mrmE+4Z/NdvDCApN+jX3uFdrySqfW70wzFbFLwWtVNkXa8ONlIvfx9Dk0xSyvYq0NpxasYJ9o8emcUVCw6EjGvuUpLXgfVm9cP1fAZp1yyCKeGBf8pB96g9jUZ57c6s1vIIAUfjXqY9eFg1yiuKJnOECzeW+TJm0+rxRGGWfcP7/dld8bZwqcp/dJqIs9hrJIJ/JD2abV5j1StfJn1/pofo/Kx0ae1KfAO7/Vld7anfVpf28M5kKPDc9kYLRW4RDhIwYV/PozVUAF39Qre3BmrvsM04nisjHHyJlUjZEOefuBj8UIA81zHfGJ84BYeHAP9LKseP1r5LNnvOlHeXJgqRZbUPzT97PHvBVb48VCX09W54du2u3ZJwjD0It/gqmCue/yoolm4b7tQjmohh7cGAWzHC8x/qOFOZmBG4bbERDkQrVYyiGP7iPwPLGrgsAofYbePonEJ2CHxAuvjxEjLvfUj7J1BaP0irY3i888SA63l3alWgwKjbXueZztOSBoucOE33huIZdsWHChXRds72O069PyHhSEBDiOynbAEBiGreCGJKoa5zT8GVBzt4QNgXc+wbq4YvW+hSMkDYNa4EYihWqlYtmouSsYTo4XvgWezHKDcI+7xuPbMMp7JH0GEfhZGRMDIG5FRtLG1IGCNvTp/d9nFZhMx/DXYH/cgSBv6SscM+Tyf0P450Lw+iCmbOGAMonOeO/XlMyTjgAsfmWAN9Y53RFy0hDAovXBDSBFBVAIHDdUJ2lre3J6AVG9Hcln5NQyKCUcrd390g5/BtjpNR2KNGwTVpRDSmk6et6jwCv0ScVhpxopxl3DBIjzVjrYk5gVuEPAaw7UP+aFV+0ex5Aq8y/hTYhiE/UXjhibrlBUisUm8hmHwqujuH3IqQLA/0dT+Af8Q34hT8du3QXlR4nrdkxhJ0554nwAXhpvj+hLUo2u/zWoJM1aXy70ZP8e97APWJ+WGbN1AXNP8tedAasM96PLu4Ik2jhpHZLkqgdGM5TNjuKzNnhkiUmneH8CSCe9wpXV429HDlCu7GcV9JwemWoEbWr3rGZx2iMs5F4+T3S1p89DoYGvkUeLCKC67m+uBsVwVuGpI+QVohGtZ6rHrU+Cu/UaP/ps4KY3iWhlipwNwd4Arh1WLCIy4lpA/2yiF4XZ9ehgMuaRgt7r6FMWiC9DuL64YWtyCrQKuEOLe1iJsG+eO2W8eo+POdrvVtdULrgG0Dbg76xW1uCDcm5GCguzDAeNlz0qPqgfzGunJeAl4aOug6KYQ7l2WhI7DZEMqZ7L5a1uBZWTQF3/QVHvmUosOBX0ZVkbfkgNtDYCbDcDVsIKbQYCJBCY/gak7FHQh+bqiX7LwsnuYfr1gqUTCUsPWgsWdF1H2I1/ZoYBMSLs3o3/blyke+FRiEPE9c1Huq9dpV60GWQNmvybSIrCnee0SGIlDJzJfVzwrttTq7bfkUNCSzV71a19pScNOGHrmi9pWV/Uue6lXYpEcBFfgslSOPG0MBTASc/YK3455PEqvyYY5r0G4AeH6gWHqSCyVxQ2s9ksJw9B/ATBYVUy8fdRL6ZhhlPo1HpIyHelM38OmCuA6oWvzwTah69DTbiW6qxdMCdPdAIGLbrC8lyIimxHRgrhQcA+cdoqluxXc0u7qhcTGNBAYeKkB9CTASfJjVuTo7mvoRsO676Ci+LRanVbd91YgLggp2GI1/kpRq7MAXnuDjBhC8Qpkl3UepwIXgblseDQq2XBcUK8bru0hGgbni7ynzrMNs1xOuJDmNQMAsfAI2B0CjOaAvKuuK2aES8C8XU8Sn98H9SKw12/SwfwVzNyArOLOL1lxEpO37/lKFujlpW3UfTSZwpxaQCkXb+JVd3OAAg1xrQ4vFGzC0MDrbuvLSGtRiSVYuonjeNU5MxMWAVudZzct1azdLmUXzGZLV7BCySxG6Zrq4MsFXqv79A7WiLu1OwwLFgElr7VA3LQjLtZnCCx7+KNo7a4BuG3lhRmKWXQ0LME40Gbxsqt6BQH3arExZ+viCl67Ib1rGHFLQPIQL7JFnHTjRfUCb68whR1mXM3dttpjcWvIAS6uNCRxlmVxxypeCVJw3wjl0/LzmrfaVG4kBgFT6ge57wJ4M7OTfmlNS4j+McpB4G2rTfBGkhAwp2UcWfB2cw/FFogBKQvxrhtTLMnMZYJiFG4eeLM0zVLRg3dIzmJvAbfRgiXjS81rXfeBLIE3TTuVQneZeH8Fb4HXFQ0rcGKJcsNFXsRdduYdViSQBQNy0LCilaSIu+R3TeqP8KKLQAXXzjgw3hR5l3erFvoldOOVr9Cv5eK6v1tzXch0UZfLNGEPvGQi3fU7tMi1m45PgCtb4Nin974Lftmd9yUtJZ94q/NgUG9KvA9rWOjgwKATMTqv3mpcbcDgQxaLRbpYyp+89/5tLMF98GTAVZsP4LfpAuXRYnALBwof+0AxejR0EVVpO4ARbvpz96D1GV7FvNoJB4lNDLiQOKofIQSTicQcnzeq5ZUsxTpi8ctQJeVrJmNj8wbEWxHhYNxjXff8UiT1vww1Oq9R59Dgz1gGb5Kff5a62jA/4tD222Ml75J4zd+8uglmfcQB76s2nktsM2w2z8p2yamWG90eTNrd9ly/ALnAtlP8LO5a1FdSo9sv7h3cVvGqGHkXT9Sr+3ZcjO4faNNYUMErkHf2tIeuqBNhjc0bHXEDoVHBa20qeRm1liw1Mq9H29z68Ard+hs7f0BzWD/3S8g7q+TV3RohR8VVLqq34pgR2G8NL9O8alx3Rrvy7Cr3q2LkXTyPClrBY55JgPqCthFGVbxsgbxxRd2jxKCGTS/zpelW0beD8pB4NxVhVw7t2HSvj0m9lfUx5A/zzWw2q0yPHzYHjWEOuDXvWLnhAtL1Gah3XrWsImkL/WjAkoX7au+r00bQ7my+qFr4ekETpFvyUGsOKOAgZrNNZaE2InCx9XF/qVmFQwNGBVevs42n31K9+5oqFxw0GURc22UayXjBenHrY1Z7UJ/FpOCkRsFjWe+SNsLuef2xCm0QMfvwe60pxnGf5v7iNTR/xWZWb8GjWcOFgBtK3FLBM+uTCpatd5aigue1Pngs4yVcp8VphmT+YYuQGIhxm/Fu37w+j0mPBk4+BIy4ett8q52lGJTneJsbHwHGwx/FQYp2Q6wtogCWH8DNLtdt0S1Pi6RICx8JG1nFCluOV9yWLgrrjAI4HfVQNtYu5emw9ri0EyZGWpCNORYxvVuAGZeHgLIuEVZB5UnAqGLryfsLvDx31Gfa6czSSW+D7XRFVZgEyizlRfEm3yJFSaiM+HQ5Ee5ll3SNVgCczkvi+SJ5c+PMMtIV0BLu6RL32P8Lry8pcVHJcZoYlniDcCNJ49Xp+/uk5QK20PP0kLWYP8qsg2zuvl/VyAlQS1bQ7SnjfQ814O7WeF4jX/P/5l//fT2V77svePeNd/gFNam/FN/eZPd9io0B/ojOwMWVsA8/wO1RZvc/nOgTbqfi7okAfDbUe+KDjcVsPq9X81eJPK/g/So476kfWUG1S6vjmcIqYpGkGwT7r4t8FfffdIP7ajmdNlnC2Qto2fWNtixjudRr4a+VLF0uTa4vJF8XKuXbg/Hr33TjffKn3gp/kkkmmWSSSSaZZJJJJplkkkkmmWSS/yf5H6HANgUotAMHAAAAAElFTkSuQmCC + mediatype: image/png install: spec: + clusterPermissions: + - rules: + - apiGroups: + - apiextensions.k8s.io + resources: + - customresourcedefinitions + verbs: + - get + - apiGroups: + - operator.ibm.com + resources: + - auditloggings + - certmanagers + verbs: + - delete + - get + - apiGroups: + - operators.coreos.com + resources: + - catalogsources + verbs: + - get + serviceAccountName: operand-deployment-lifecycle-manager deployments: - - label: - app.kubernetes.io/instance: operand-deployment-lifecycle-manager - app.kubernetes.io/managed-by: operand-deployment-lifecycle-manager - app.kubernetes.io/name: operand-deployment-lifecycle-manager - name: operand-deployment-lifecycle-manager - spec: - replicas: 1 - selector: - matchLabels: - name: operand-deployment-lifecycle-manager - strategy: {} - template: - metadata: - annotations: - productID: 068a62892a1e4db39641342e592daa25 - productMetric: FREE - productName: IBM Cloud Platform Common Services - labels: - app.kubernetes.io/instance: operand-deployment-lifecycle-manager - app.kubernetes.io/managed-by: operand-deployment-lifecycle-manager - app.kubernetes.io/name: operand-deployment-lifecycle-manager - intent: projected-odlm + - label: + app.kubernetes.io/instance: operand-deployment-lifecycle-manager + app.kubernetes.io/managed-by: operand-deployment-lifecycle-manager + app.kubernetes.io/name: operand-deployment-lifecycle-manager + productName: IBM_Cloud_Platform_Common_Services + name: operand-deployment-lifecycle-manager + spec: + replicas: 1 + selector: + matchLabels: name: operand-deployment-lifecycle-manager - spec: - affinity: - nodeAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - nodeSelectorTerms: - - matchExpressions: - - key: kubernetes.io/arch - operator: In - values: - - amd64 - - ppc64le - - s390x - containers: - - args: - - -v=1 - command: - - /manager - env: - - name: OPERATOR_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace - - name: WATCH_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.annotations['olm.targetNamespaces'] - image: icr.io/cpopen/odlm:latest - imagePullPolicy: Always - livenessProbe: - failureThreshold: 10 - httpGet: - path: /readyz - port: 8081 - initialDelaySeconds: 120 - periodSeconds: 60 - timeoutSeconds: 10 - name: manager - readinessProbe: - failureThreshold: 10 - httpGet: - path: /healthz - port: 8081 - initialDelaySeconds: 5 - periodSeconds: 20 - timeoutSeconds: 3 - resources: - limits: - cpu: 500m - memory: 512Mi - requests: - cpu: 200m - memory: 200Mi - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: - - ALL - privileged: false - readOnlyRootFilesystem: true - runAsNonRoot: true - serviceAccount: operand-deployment-lifecycle-manager - serviceAccountName: operand-deployment-lifecycle-manager - terminationGracePeriodSeconds: 10 + strategy: {} + template: + metadata: + annotations: + productID: 068a62892a1e4db39641342e592daa25 + productMetric: FREE + productName: IBM Cloud Platform Common Services + labels: + app.kubernetes.io/instance: operand-deployment-lifecycle-manager + app.kubernetes.io/managed-by: operand-deployment-lifecycle-manager + app.kubernetes.io/name: operand-deployment-lifecycle-manager + intent: projected-odlm + name: operand-deployment-lifecycle-manager + productName: IBM_Cloud_Platform_Common_Services + spec: + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: kubernetes.io/arch + operator: In + values: + - amd64 + - ppc64le + - s390x + containers: + - args: + - -v=1 + command: + - /manager + env: + - name: OPERATOR_NAMESPACE + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.namespace + - name: WATCH_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.annotations['olm.targetNamespaces'] + image: icr.io/cpopen/odlm:latest + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 10 + httpGet: + path: /readyz + port: 8081 + initialDelaySeconds: 120 + periodSeconds: 60 + timeoutSeconds: 10 + name: manager + readinessProbe: + failureThreshold: 10 + httpGet: + path: /healthz + port: 8081 + initialDelaySeconds: 5 + periodSeconds: 20 + timeoutSeconds: 3 + resources: + limits: + cpu: 500m + memory: 512Mi + requests: + cpu: 200m + ephemeral-storage: 256Mi + memory: 200Mi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + serviceAccount: operand-deployment-lifecycle-manager + serviceAccountName: operand-deployment-lifecycle-manager + terminationGracePeriodSeconds: 10 permissions: - - rules: - - apiGroups: - - '*' - resources: - - '*' - verbs: - - create - - delete - - get - - list - - patch - - update - - watch - serviceAccountName: operand-deployment-lifecycle-manager + - rules: + - apiGroups: + - "" + resources: + - configmaps + - namespaces + - secrets + - services + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - '*' + resources: + - '*' + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - k8s.keycloak.org + resources: + - keycloakrealmimports + - keycloaks + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - operator.ibm.com + resources: + - operandbindinfos + - operandbindinfos/finalizers + - operandbindinfos/status + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - operator.ibm.com + resources: + - operandconfigs + - operandconfigs/finalizers + - operandconfigs/status + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - operator.ibm.com + resources: + - operandregistries + - operandregistries/finalizers + - operandregistries/status + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - operator.ibm.com + resources: + - operandrequests + - operandrequests/finalizers + - operandrequests/status + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - operator.ibm.com + resources: + - operatorconfigs + - operatorconfigs/finalizers + - operatorconfigs/status + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - operators.coreos.com + resources: + - clusterserviceversions + - subscriptions + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - operators.coreos.com + resources: + - installplans + - operatorgroups + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - packages.operators.coreos.com + resources: + - packagemanifests + verbs: + - get + - list + - patch + - update + - watch + - apiGroups: + - route.openshift.io + resources: + - routes + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + serviceAccountName: operand-deployment-lifecycle-manager strategy: deployment installModes: - - supported: true - type: OwnNamespace - - supported: true - type: SingleNamespace - - supported: true - type: MultiNamespace - - supported: true - type: AllNamespaces + - supported: true + type: OwnNamespace + - supported: true + type: SingleNamespace + - supported: true + type: MultiNamespace + - supported: true + type: AllNamespaces keywords: - - IBM - - Cloud + - IBM + - Cloud labels: name: operand-deployment-lifecycle-manager links: - - name: IBM Operand Deployment Lifecycle Manager Project - url: https://github.com/IBM/operand-deployment-lifecycle-manager + - name: IBM Operand Deployment Lifecycle Manager Project + url: https://github.com/IBM/operand-deployment-lifecycle-manager maintainers: - - email: support@ibm.com - name: IBM Support + - email: support@ibm.com + name: IBM Support maturity: stable minKubeVersion: 1.19.0 provider: name: IBM + version: 4.4.0 relatedImages: - - image: icr.io/cpopen/odlm:4.0.0 - name: ODLM_IMAGE - version: 4.0.0 + - image: icr.io/cpopen/odlm:4.4.0 + name: ODLM_IMAGE diff --git a/bundle/manifests/operator.ibm.com_operandbindinfos.yaml b/bundle/manifests/operator.ibm.com_operandbindinfos.yaml index 18b7184f..bea8cfed 100644 --- a/bundle/manifests/operator.ibm.com_operandbindinfos.yaml +++ b/bundle/manifests/operator.ibm.com_operandbindinfos.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.1 + controller-gen.kubebuilder.io/version: v0.14.0 creationTimestamp: null labels: app.kubernetes.io/instance: operand-deployment-lifecycle-manager @@ -35,16 +35,24 @@ spec: schema: openAPIV3Schema: description: OperandBindInfo is the Schema for the operandbindinfoes API. + Documentation For additional details regarding install parameters check + https://ibm.biz/icpfs39install. License By installing this product you accept + the license terms https://ibm.biz/icpfs39license properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -53,17 +61,56 @@ spec: properties: bindings: additionalProperties: - description: SecretConfigmap is a pair of Secret and/or Configmap. + description: |- + Bindable is a Kubernetes resources to be shared from one namespace to another. + List of supported resources are Secrets, Configmaps, Services, and Routes. + Secrets and Configmaps will be copied such that a new Secret/Configmap with + exactly the same data will be created in the target namespace. + Services and Routes data will be copied into a configmap in the target + namespace. properties: configmap: description: The configmap identifies an existing configmap object. if it exists, the ODLM will share to the namespace of the OperandRequest. type: string + route: + description: |- + Route data will be shared by copying it into a configmap which is then + created in the target namespace + properties: + data: + additionalProperties: + type: string + description: |- + Data is a key-value pair where the value is a YAML path to a value in the + OpenShift Route, e.g. .spec.host or .spec.tls.termination + type: object + name: + description: Name is the name of the OpenShift Route resource + type: string + type: object secret: description: The secret identifies an existing secret. if it exists, the ODLM will share to the namespace of the OperandRequest. type: string + service: + description: |- + Service data will be shared by copying it into a configmap which is then + created in the target namespace + properties: + data: + additionalProperties: + type: string + description: |- + Data is a key-value pair where the value is a YAML path to a value in the + Kubernetes Service, e.g. .spec.ports[0]port + type: object + name: + description: Name is the name of the Kubernetes Service + resource + type: string + type: object type: object description: The bindings section is used to specify information about the access/configuration data that is to be shared. @@ -71,7 +118,8 @@ spec: description: type: string operand: - description: The deployed service identifies itself with its operand. + description: |- + The deployed service identifies itself with its operand. This must match the name in the OperandRegistry in the current namespace. type: string registry: @@ -79,9 +127,9 @@ spec: CR from which this operand deployment is being requested. type: string registryNamespace: - description: Specifies the namespace in which the OperandRegistry - reside. The default is the current namespace in which the request - is defined. + description: |- + Specifies the namespace in which the OperandRegistry reside. + The default is the current namespace in which the request is defined. type: string required: - operand @@ -100,6 +148,7 @@ spec: type: string type: array type: object + x-kubernetes-preserve-unknown-fields: true type: object served: true storage: true @@ -109,5 +158,5 @@ status: acceptedNames: kind: "" plural: "" - conditions: [] - storedVersions: [] + conditions: null + storedVersions: null diff --git a/bundle/manifests/operator.ibm.com_operandconfigs.yaml b/bundle/manifests/operator.ibm.com_operandconfigs.yaml index 03a8acbd..3df97861 100644 --- a/bundle/manifests/operator.ibm.com_operandconfigs.yaml +++ b/bundle/manifests/operator.ibm.com_operandconfigs.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.1 + controller-gen.kubebuilder.io/version: v0.14.0 creationTimestamp: null labels: app.kubernetes.io/instance: operand-deployment-lifecycle-manager @@ -34,17 +34,24 @@ spec: name: v1alpha1 schema: openAPIV3Schema: - description: OperandConfig is the Schema for the operandconfigs API. + description: OperandConfig is the Schema for the operandconfigs API. Documentation + For additional details regarding install parameters check https://ibm.biz/icpfs39install. + License By installing this product you accept the license terms https://ibm.biz/icpfs39license properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -102,11 +109,143 @@ spec: namespace: description: Namespace is the namespace of the resource. type: string + optionalFields: + description: OptionalFields is the list of fields that + could be updated additionally. + items: + description: OptionalField defines the optional field + for the resource. + properties: + matchExpressions: + description: MatchExpressions is the match expression + of the field. + items: + description: MatchExpression defines the match + expression of the field. + properties: + key: + description: Key is the key of the field. + type: string + objectRef: + description: ObjectRef is the reference of + the object. + properties: + apiVersion: + description: APIVersion is the version + of the object. + type: string + kind: + description: Kind is the kind of the object. + type: string + name: + description: Name is the name of the object. + type: string + namespace: + description: Namespace is the namespace + of the object. + type: string + required: + - apiVersion + - kind + - name + type: object + operator: + description: Operator is the operator of the + field. + type: string + values: + description: Values is the values of the field. + items: + type: string + type: array + required: + - key + - operator + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + operation: + description: Operation is the operation of the field. + type: string + path: + description: Path is the json path of the field. + type: string + valueFrom: + description: ValueFrom is the field value from the + object + properties: + objectRef: + description: ObjectRef is the reference of the + object. + properties: + apiVersion: + description: APIVersion is the version of + the object. + type: string + kind: + description: Kind is the kind of the object. + type: string + name: + description: Name is the name of the object. + type: string + namespace: + description: Namespace is the namespace + of the object. + type: string + required: + - apiVersion + - kind + - name + type: object + path: + description: Path is the json path of the field. + type: string + required: + - path + type: object + required: + - operation + - path + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + ownerReferences: + description: OwnerReferences is the list of owner references. + items: + properties: + apiVersion: + description: API version of the referent. + type: string + blockOwnerDeletion: + description: |- + If true, AND if the owner has the "foregroundDeletion" finalizer, then + the owner cannot be deleted from the key-value store until this + reference is removed. + Defaults to false. + type: boolean + controller: + description: |- + If true, this reference points to the managing controller. + Default is false. + type: boolean + kind: + description: Kind of the referent. + type: string + name: + description: Name of the referent. + type: string + required: + - apiVersion + - kind + - name + type: object + type: array required: - apiVersion - kind - name type: object + x-kubernetes-preserve-unknown-fields: true type: array spec: additionalProperties: @@ -143,6 +282,7 @@ spec: description: ServiceStatus defines all the status of a operator. type: object type: object + x-kubernetes-preserve-unknown-fields: true type: object served: true storage: true @@ -152,5 +292,5 @@ status: acceptedNames: kind: "" plural: "" - conditions: [] - storedVersions: [] + conditions: null + storedVersions: null diff --git a/bundle/manifests/operator.ibm.com_operandregistries.yaml b/bundle/manifests/operator.ibm.com_operandregistries.yaml index e1dd4e1b..be2020d6 100644 --- a/bundle/manifests/operator.ibm.com_operandregistries.yaml +++ b/bundle/manifests/operator.ibm.com_operandregistries.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.1 + controller-gen.kubebuilder.io/version: v0.14.0 creationTimestamp: null labels: app.kubernetes.io/instance: operand-deployment-lifecycle-manager @@ -35,16 +35,24 @@ spec: schema: openAPIV3Schema: description: OperandRegistry is the Schema for the operandregistries API. + Documentation For additional details regarding install parameters check + https://ibm.biz/icpfs39install. License By installing this product you accept + the license terms https://ibm.biz/icpfs39license properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -62,36 +70,48 @@ spec: description: description: Description of a common service. type: string + fallbackChannels: + description: List of channels to fallback when the main channel + is not available. + items: + type: string + type: array installMode: - description: 'The install mode of an operator, either namespace - or cluster. Valid values are: - "namespace" (default): operator - is deployed in namespace of OperandRegistry; - "cluster": - operator is deployed in "openshift-operators" namespace;' + description: |- + The install mode of an operator, either namespace or cluster. + Valid values are: + - "namespace" (default): operator is deployed in namespace of OperandRegistry; + - "cluster": operator is deployed in "openshift-operators" namespace; + - "no-op": operator is not supported to be fresh deployed; type: string installPlanApproval: - description: 'Approval mode for emitted InstallPlans. Valid - values are: - "Automatic" (default): operator will be installed - automatically; - "Manual": operator installation will be pending - until users approve it;' + description: |- + Approval mode for emitted InstallPlans. + Valid values are: + - "Automatic" (default): operator will be installed automatically; + - "Manual": operator installation will be pending until users approve it; type: string name: description: A unique name for the operator whose operand may be deployed. type: string namespace: - description: The namespace in which operator should be deployed - when InstallMode is empty or set to "namespace". If the namespace - is not set, the operator namespace is the same as OperandRegistry - Namespace + description: |- + The namespace in which operator should be deployed when InstallMode is empty or set to "namespace". + If the namespace is not set, the operator namespace is the same as OperandRegistry Namespace + type: string + operatorConfig: + description: OperatorConfig is the name of the OperatorConfig type: string packageName: description: Name of the package that defines the applications. type: string scope: - description: 'A scope indicator, either public or private. Valid - values are: - "private" (default): deployment only request - from the containing names; - "public": deployment can be requested - from other namespaces;' + description: |- + A scope indicator, either public or private. + Valid values are: + - "private" (default): deployment only request from the containing names; + - "public": deployment can be requested from other namespaces; enum: - public - private @@ -112,8 +132,9 @@ spec: configuration. properties: env: - description: Env is a list of environment variables to set - in the container. Cannot be updated. + description: |- + Env is a list of environment variables to set in the container. + Cannot be updated. items: description: EnvVar represents an environment variable present in a Container. @@ -123,15 +144,16 @@ spec: be a C_IDENTIFIER. type: string value: - description: 'Variable references $(VAR_NAME) are - expanded using the previous defined environment - variables in the container and any service environment - variables. If a variable cannot be resolved, the - reference in the input string will be unchanged. - The $(VAR_NAME) syntax can be escaped with a double - $$, ie: $$(VAR_NAME). Escaped references will never - be expanded, regardless of whether the variable - exists or not. Defaults to "".' + description: |- + Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in the container and + any service environment variables. If a variable cannot be resolved, + the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless of whether the variable + exists or not. + Defaults to "". type: string valueFrom: description: Source for the environment variable's @@ -144,10 +166,10 @@ spec: description: The key to select. type: string name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? type: string optional: description: Specify whether the ConfigMap @@ -156,12 +178,11 @@ spec: required: - key type: object + x-kubernetes-map-type: atomic fieldRef: - description: 'Selects a field of the pod: supports - metadata.name, metadata.namespace, `metadata.labels['''']`, - `metadata.annotations['''']`, spec.nodeName, - spec.serviceAccountName, status.hostIP, status.podIP, - status.podIPs.' + description: |- + Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, + spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. properties: apiVersion: description: Version of the schema the FieldPath @@ -174,12 +195,11 @@ spec: required: - fieldPath type: object + x-kubernetes-map-type: atomic resourceFieldRef: - description: 'Selects a resource of the container: - only resources limits and requests (limits.cpu, - limits.memory, limits.ephemeral-storage, requests.cpu, - requests.memory and requests.ephemeral-storage) - are currently supported.' + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. properties: containerName: description: 'Container name: required for @@ -199,6 +219,7 @@ spec: required: - resource type: object + x-kubernetes-map-type: atomic secretKeyRef: description: Selects a key of a secret in the pod's namespace @@ -208,10 +229,10 @@ spec: from. Must be a valid secret key. type: string name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? type: string optional: description: Specify whether the Secret or @@ -220,19 +241,20 @@ spec: required: - key type: object + x-kubernetes-map-type: atomic type: object required: - name type: object type: array envFrom: - description: EnvFrom is a list of sources to populate environment - variables in the container. The keys defined within a - source must be a C_IDENTIFIER. All invalid keys will be - reported as an event when the container is starting. When - a key exists in multiple sources, the value associated - with the last source will take precedence. Values defined - by an Env with a duplicate key will take precedence. Immutable. + description: |- + EnvFrom is a list of sources to populate environment variables in the container. + The keys defined within a source must be a C_IDENTIFIER. All invalid keys + will be reported as an event when the container is starting. When a key exists in multiple + sources, the value associated with the last source will take precedence. + Values defined by an Env with a duplicate key will take precedence. + Immutable. items: description: EnvFromSource represents the source of a set of ConfigMaps @@ -241,16 +263,17 @@ spec: description: The ConfigMap to select from properties: name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? type: string optional: description: Specify whether the ConfigMap must be defined type: boolean type: object + x-kubernetes-map-type: atomic prefix: description: An optional identifier to prepend to each key in the ConfigMap. Must be a C_IDENTIFIER. @@ -259,29 +282,32 @@ spec: description: The Secret to select from properties: name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? type: string optional: description: Specify whether the Secret must be defined type: boolean type: object + x-kubernetes-map-type: atomic type: object type: array nodeSelector: additionalProperties: type: string - description: 'NodeSelector is a selector which must be true - for the pod to fit on a node. Selector which must match - a node''s labels for the pod to be scheduled on that node. - More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' + description: |- + NodeSelector is a selector which must be true for the pod to fit on a node. + Selector which must match a node's labels for the pod to be scheduled on that node. + More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ type: object resources: - description: 'Resources represents compute resources required - by this container. Immutable. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + description: |- + Resources represents compute resources required by this container. + Immutable. + More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/ properties: limits: additionalProperties: @@ -290,8 +316,9 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: 'Limits describes the maximum amount of - compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -300,25 +327,26 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: 'Requests describes the minimum amount - of compute resources required. If Requests is omitted - for a container, it defaults to Limits if that is - explicitly specified, otherwise to an implementation-defined - value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object selector: - description: Selector is the label selector for pods to - be configured. Existing ReplicaSets whose pods are selected - by this will be the ones affected by this deployment. + description: |- + Selector is the label selector for pods to be configured. + Existing ReplicaSets whose pods are + selected by this will be the ones affected by this deployment. It must match the pod template's labels. properties: matchExpressions: description: matchExpressions is a list of label selector requirements. The requirements are ANDed. items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. properties: key: @@ -326,17 +354,16 @@ spec: applies to. type: string operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists and DoesNotExist. + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. type: string values: - description: values is an array of string values. - If the operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be empty. - This array is replaced during a strategic merge - patch. + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. items: type: string type: array @@ -348,53 +375,49 @@ spec: matchLabels: additionalProperties: type: string - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field - is "key", the operator is "In", and the values array - contains only "value". The requirements are ANDed. + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic tolerations: description: Tolerations are the pod's tolerations. items: - description: The pod this Toleration is attached to tolerates - any taint that matches the triple - using the matching operator . + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . properties: effect: - description: Effect indicates the taint effect to - match. Empty means match all taint effects. When - specified, allowed values are NoSchedule, PreferNoSchedule - and NoExecute. + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. type: string key: - description: Key is the taint key that the toleration - applies to. Empty means match all taint keys. If - the key is empty, operator must be Exists; this - combination means to match all values and all keys. + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. type: string operator: - description: Operator represents a key's relationship - to the value. Valid operators are Exists and Equal. - Defaults to Equal. Exists is equivalent to wildcard - for value, so that a pod can tolerate all taints - of a particular category. + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. type: string tolerationSeconds: - description: TolerationSeconds represents the period - of time the toleration (which must be of effect - NoExecute, otherwise this field is ignored) tolerates - the taint. By default, it is not set, which means - tolerate the taint forever (do not evict). Zero - and negative values will be treated as 0 (evict - immediately) by the system. + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. format: int64 type: integer value: - description: Value is the taint value the toleration - matches to. If the operator is Exists, the value - should be empty, otherwise just a regular string. + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. type: string type: object type: array @@ -405,34 +428,36 @@ spec: within a container. properties: mountPath: - description: Path within the container at which the - volume should be mounted. Must not contain ':'. + description: |- + Path within the container at which the volume should be mounted. Must + not contain ':'. type: string mountPropagation: - description: mountPropagation determines how mounts - are propagated from the host to container and the - other way around. When not set, MountPropagationNone - is used. This field is beta in 1.10. + description: |- + mountPropagation determines how mounts are propagated from the host + to container and the other way around. + When not set, MountPropagationNone is used. + This field is beta in 1.10. type: string name: description: This must match the Name of a Volume. type: string readOnly: - description: Mounted read-only if true, read-write - otherwise (false or unspecified). Defaults to false. + description: |- + Mounted read-only if true, read-write otherwise (false or unspecified). + Defaults to false. type: boolean subPath: - description: Path within the volume from which the - container's volume should be mounted. Defaults to - "" (volume's root). + description: |- + Path within the volume from which the container's volume should be mounted. + Defaults to "" (volume's root). type: string subPathExpr: - description: Expanded path within the volume from - which the container's volume should be mounted. - Behaves similarly to SubPath but environment variable - references $(VAR_NAME) are expanded using the container's - environment. Defaults to "" (volume's root). SubPathExpr - and SubPath are mutually exclusive. + description: |- + Expanded path within the volume from which the container's volume should be mounted. + Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. + Defaults to "" (volume's root). + SubPathExpr and SubPath are mutually exclusive. type: string required: - mountPath @@ -446,234 +471,233 @@ spec: that may be accessed by any container in the pod. properties: awsElasticBlockStore: - description: 'AWSElasticBlockStore represents an AWS - Disk resource that is attached to a kubelet''s host - machine and then exposed to the pod. More info: - https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + description: |- + awsElasticBlockStore represents an AWS Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore properties: fsType: - description: 'Filesystem type of the volume that - you want to mount. Tip: Ensure that the filesystem - type is supported by the host operating system. - Examples: "ext4", "xfs", "ntfs". Implicitly - inferred to be "ext4" if unspecified. More info: - https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore - TODO: how do we prevent errors in the filesystem - from compromising the machine' + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + TODO: how do we prevent errors in the filesystem from compromising the machine type: string partition: - description: 'The partition in the volume that - you want to mount. If omitted, the default is - to mount by volume name. Examples: For volume - /dev/sda1, you specify the partition as "1". - Similarly, the volume partition for /dev/sda - is "0" (or you can leave the property empty).' + description: |- + partition is the partition in the volume that you want to mount. + If omitted, the default is to mount by volume name. + Examples: For volume /dev/sda1, you specify the partition as "1". + Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty). format: int32 type: integer readOnly: - description: 'Specify "true" to force and set - the ReadOnly property in VolumeMounts to "true". - If omitted, the default is "false". More info: - https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + description: |- + readOnly value true will force the readOnly setting in VolumeMounts. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore type: boolean volumeID: - description: 'Unique ID of the persistent disk - resource in AWS (Amazon EBS volume). More info: - https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + description: |- + volumeID is unique ID of the persistent disk resource in AWS (Amazon EBS volume). + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore type: string required: - volumeID type: object azureDisk: - description: AzureDisk represents an Azure Data Disk + description: azureDisk represents an Azure Data Disk mount on the host and bind mount to the pod. properties: cachingMode: - description: 'Host Caching mode: None, Read Only, - Read Write.' + description: 'cachingMode is the Host Caching + mode: None, Read Only, Read Write.' type: string diskName: - description: The Name of the data disk in the - blob storage + description: diskName is the Name of the data + disk in the blob storage type: string diskURI: - description: The URI the data disk in the blob - storage + description: diskURI is the URI of data disk in + the blob storage type: string fsType: - description: Filesystem type to mount. Must be - a filesystem type supported by the host operating - system. Ex. "ext4", "xfs", "ntfs". Implicitly - inferred to be "ext4" if unspecified. + description: |- + fsType is Filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. type: string kind: - description: 'Expected values Shared: multiple - blob disks per storage account Dedicated: single - blob disk per storage account Managed: azure - managed data disk (only in managed availability + description: 'kind expected values are Shared: + multiple blob disks per storage account Dedicated: + single blob disk per storage account Managed: + azure managed data disk (only in managed availability set). defaults to shared' type: string readOnly: - description: Defaults to false (read/write). ReadOnly - here will force the ReadOnly setting in VolumeMounts. + description: |- + readOnly Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. type: boolean required: - diskName - diskURI type: object azureFile: - description: AzureFile represents an Azure File Service + description: azureFile represents an Azure File Service mount on the host and bind mount to the pod. properties: readOnly: - description: Defaults to false (read/write). ReadOnly - here will force the ReadOnly setting in VolumeMounts. + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. type: boolean secretName: - description: the name of secret that contains - Azure Storage Account Name and Key + description: secretName is the name of secret + that contains Azure Storage Account Name and + Key type: string shareName: - description: Share Name + description: shareName is the azure share Name type: string required: - secretName - shareName type: object cephfs: - description: CephFS represents a Ceph FS mount on + description: cephFS represents a Ceph FS mount on the host that shares a pod's lifetime properties: monitors: - description: 'Required: Monitors is a collection - of Ceph monitors More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + description: |- + monitors is Required: Monitors is a collection of Ceph monitors + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it items: type: string type: array path: - description: 'Optional: Used as the mounted root, - rather than the full Ceph tree, default is /' + description: 'path is Optional: Used as the mounted + root, rather than the full Ceph tree, default + is /' type: string readOnly: - description: 'Optional: Defaults to false (read/write). - ReadOnly here will force the ReadOnly setting - in VolumeMounts. More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + description: |- + readOnly is Optional: Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it type: boolean secretFile: - description: 'Optional: SecretFile is the path - to key ring for User, default is /etc/ceph/user.secret - More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + description: |- + secretFile is Optional: SecretFile is the path to key ring for User, default is /etc/ceph/user.secret + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it type: string secretRef: - description: 'Optional: SecretRef is reference - to the authentication secret for User, default - is empty. More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + description: |- + secretRef is Optional: SecretRef is reference to the authentication secret for User, default is empty. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it properties: name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? type: string type: object + x-kubernetes-map-type: atomic user: - description: 'Optional: User is the rados user - name, default is admin More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + description: |- + user is optional: User is the rados user name, default is admin + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it type: string required: - monitors type: object cinder: - description: 'Cinder represents a cinder volume attached - and mounted on kubelets host machine. More info: - https://examples.k8s.io/mysql-cinder-pd/README.md' + description: |- + cinder represents a cinder volume attached and mounted on kubelets host machine. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md properties: fsType: - description: 'Filesystem type to mount. Must be - a filesystem type supported by the host operating - system. Examples: "ext4", "xfs", "ntfs". Implicitly - inferred to be "ext4" if unspecified. More info: - https://examples.k8s.io/mysql-cinder-pd/README.md' + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md type: string readOnly: - description: 'Optional: Defaults to false (read/write). - ReadOnly here will force the ReadOnly setting - in VolumeMounts. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md type: boolean secretRef: - description: 'Optional: points to a secret object - containing parameters used to connect to OpenStack.' + description: |- + secretRef is optional: points to a secret object containing parameters used to connect + to OpenStack. properties: name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? type: string type: object + x-kubernetes-map-type: atomic volumeID: - description: 'volume id used to identify the volume - in cinder. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + description: |- + volumeID used to identify the volume in cinder. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md type: string required: - volumeID type: object configMap: - description: ConfigMap represents a configMap that + description: configMap represents a configMap that should populate this volume properties: defaultMode: - description: 'Optional: mode bits used to set - permissions on created files by default. Must - be an octal value between 0000 and 0777 or a - decimal value between 0 and 511. YAML accepts - both octal and decimal values, JSON requires - decimal values for mode bits. Defaults to 0644. - Directories within the path are not affected - by this setting. This might be in conflict with - other options that affect the file mode, like - fsGroup, and the result can be other mode bits - set.' + description: |- + defaultMode is optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. format: int32 type: integer items: - description: If unspecified, each key-value pair - in the Data field of the referenced ConfigMap - will be projected into the volume as a file - whose name is the key and content is the value. - If specified, the listed keys will be projected - into the specified paths, and unlisted keys - will not be present. If a key is specified which - is not present in the ConfigMap, the volume - setup will error unless it is marked optional. - Paths must be relative and may not contain the - '..' path or start with '..'. + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. items: description: Maps a string key to a path within a volume. properties: key: - description: The key to project. + description: key is the key to project. type: string mode: - description: 'Optional: mode bits used to - set permissions on this file. Must be - an octal value between 0000 and 0777 or - a decimal value between 0 and 511. YAML - accepts both octal and decimal values, - JSON requires decimal values for mode - bits. If not specified, the volume defaultMode - will be used. This might be in conflict - with other options that affect the file - mode, like fsGroup, and the result can - be other mode bits set.' + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. format: int32 type: integer path: - description: The relative path of the file - to map the key to. May not be an absolute - path. May not contain the path element - '..'. May not start with the string '..'. + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. type: string required: - key @@ -681,82 +705,78 @@ spec: type: object type: array name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? type: string optional: - description: Specify whether the ConfigMap or - its keys must be defined + description: optional specify whether the ConfigMap + or its keys must be defined type: boolean type: object + x-kubernetes-map-type: atomic csi: - description: CSI (Container Storage Interface) represents + description: csi (Container Storage Interface) represents ephemeral storage that is handled by certain external CSI drivers (Beta feature). properties: driver: - description: Driver is the name of the CSI driver - that handles this volume. Consult with your - admin for the correct name as registered in - the cluster. + description: |- + driver is the name of the CSI driver that handles this volume. + Consult with your admin for the correct name as registered in the cluster. type: string fsType: - description: Filesystem type to mount. Ex. "ext4", - "xfs", "ntfs". If not provided, the empty value - is passed to the associated CSI driver which - will determine the default filesystem to apply. + description: |- + fsType to mount. Ex. "ext4", "xfs", "ntfs". + If not provided, the empty value is passed to the associated CSI driver + which will determine the default filesystem to apply. type: string nodePublishSecretRef: - description: NodePublishSecretRef is a reference - to the secret object containing sensitive information - to pass to the CSI driver to complete the CSI + description: |- + nodePublishSecretRef is a reference to the secret object containing + sensitive information to pass to the CSI driver to complete the CSI NodePublishVolume and NodeUnpublishVolume calls. - This field is optional, and may be empty if - no secret is required. If the secret object - contains more than one secret, all secret references - are passed. + This field is optional, and may be empty if no secret is required. If the + secret object contains more than one secret, all secret references are passed. properties: name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? type: string type: object + x-kubernetes-map-type: atomic readOnly: - description: Specifies a read-only configuration - for the volume. Defaults to false (read/write). + description: |- + readOnly specifies a read-only configuration for the volume. + Defaults to false (read/write). type: boolean volumeAttributes: additionalProperties: type: string - description: VolumeAttributes stores driver-specific - properties that are passed to the CSI driver. - Consult your driver's documentation for supported - values. + description: |- + volumeAttributes stores driver-specific properties that are passed to the CSI + driver. Consult your driver's documentation for supported values. type: object required: - driver type: object downwardAPI: - description: DownwardAPI represents downward API about + description: downwardAPI represents downward API about the pod that should populate this volume properties: defaultMode: - description: 'Optional: mode bits to use on created - files by default. Must be a Optional: mode bits - used to set permissions on created files by - default. Must be an octal value between 0000 - and 0777 or a decimal value between 0 and 511. - YAML accepts both octal and decimal values, - JSON requires decimal values for mode bits. - Defaults to 0644. Directories within the path - are not affected by this setting. This might - be in conflict with other options that affect - the file mode, like fsGroup, and the result - can be other mode bits set.' + description: |- + Optional: mode bits to use on created files by default. Must be a + Optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. format: int32 type: integer items: @@ -784,18 +804,15 @@ spec: required: - fieldPath type: object + x-kubernetes-map-type: atomic mode: - description: 'Optional: mode bits used to - set permissions on this file, must be - an octal value between 0000 and 0777 or - a decimal value between 0 and 511. YAML - accepts both octal and decimal values, - JSON requires decimal values for mode - bits. If not specified, the volume defaultMode - will be used. This might be in conflict - with other options that affect the file - mode, like fsGroup, and the result can - be other mode bits set.' + description: |- + Optional: mode bits used to set permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. format: int32 type: integer path: @@ -807,10 +824,9 @@ spec: with ''..''' type: string resourceFieldRef: - description: 'Selects a resource of the - container: only resources limits and requests - (limits.cpu, limits.memory, requests.cpu - and requests.memory) are currently supported.' + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. properties: containerName: description: 'Container name: required @@ -832,129 +848,131 @@ spec: required: - resource type: object + x-kubernetes-map-type: atomic required: - path type: object type: array type: object emptyDir: - description: 'EmptyDir represents a temporary directory - that shares a pod''s lifetime. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' + description: |- + emptyDir represents a temporary directory that shares a pod's lifetime. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir properties: medium: - description: 'What type of storage medium should - back this directory. The default is "" which - means to use the node''s default medium. Must - be an empty string (default) or Memory. More - info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' + description: |- + medium represents what type of storage medium should back this directory. + The default is "" which means to use the node's default medium. + Must be an empty string (default) or Memory. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir type: string sizeLimit: anyOf: - type: integer - type: string - description: 'Total amount of local storage required - for this EmptyDir volume. The size limit is - also applicable for memory medium. The maximum - usage on memory medium EmptyDir would be the - minimum value between the SizeLimit specified - here and the sum of memory limits of all containers - in a pod. The default is nil which means that - the limit is undefined. More info: http://kubernetes.io/docs/user-guide/volumes#emptydir' + description: |- + sizeLimit is the total amount of local storage required for this EmptyDir volume. + The size limit is also applicable for memory medium. + The maximum usage on memory medium EmptyDir would be the minimum value between + the SizeLimit specified here and the sum of memory limits of all containers in a pod. + The default is nil which means that the limit is undefined. + More info: http://kubernetes.io/docs/user-guide/volumes#emptydir pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true type: object ephemeral: - description: "Ephemeral represents a volume that is - handled by a cluster storage driver. The volume's - lifecycle is tied to the pod that defines it - it - will be created before the pod starts, and deleted - when the pod is removed. \n Use this if: a) the - volume is only needed while the pod runs, b) features - of normal volumes like restoring from snapshot or - capacity tracking are needed, c) the storage - driver is specified through a storage class, and - d) the storage driver supports dynamic volume provisioning - through a PersistentVolumeClaim (see EphemeralVolumeSource - for more information on the connection between - this volume type and PersistentVolumeClaim). - \n Use PersistentVolumeClaim or one of the vendor-specific - APIs for volumes that persist for longer than the - lifecycle of an individual pod. \n Use CSI for light-weight - local ephemeral volumes if the CSI driver is meant - to be used that way - see the documentation of the - driver for more information. \n A pod can use both - types of ephemeral volumes and persistent volumes - at the same time. \n This is a beta feature and - only available when the GenericEphemeralVolume feature - gate is enabled." + description: |- + ephemeral represents a volume that is handled by a cluster storage driver. + The volume's lifecycle is tied to the pod that defines it - it will be created before the pod starts, + and deleted when the pod is removed. + + + Use this if: + a) the volume is only needed while the pod runs, + b) features of normal volumes like restoring from snapshot or capacity + tracking are needed, + c) the storage driver is specified through a storage class, and + d) the storage driver supports dynamic volume provisioning through + a PersistentVolumeClaim (see EphemeralVolumeSource for more + information on the connection between this volume type + and PersistentVolumeClaim). + + + Use PersistentVolumeClaim or one of the vendor-specific + APIs for volumes that persist for longer than the lifecycle + of an individual pod. + + + Use CSI for light-weight local ephemeral volumes if the CSI driver is meant to + be used that way - see the documentation of the driver for + more information. + + + A pod can use both types of ephemeral volumes and + persistent volumes at the same time. properties: volumeClaimTemplate: - description: "Will be used to create a stand-alone - PVC to provision the volume. The pod in which - this EphemeralVolumeSource is embedded will - be the owner of the PVC, i.e. the PVC will be - deleted together with the pod. The name of - the PVC will be `-` where - `` is the name from the `PodSpec.Volumes` - array entry. Pod validation will reject the - pod if the concatenated name is not valid for - a PVC (for example, too long). \n An existing - PVC with that name that is not owned by the - pod will *not* be used for the pod to avoid - using an unrelated volume by mistake. Starting - the pod is then blocked until the unrelated - PVC is removed. If such a pre-created PVC is - meant to be used by the pod, the PVC has to - updated with an owner reference to the pod once - the pod exists. Normally this should not be - necessary, but it may be useful when manually - reconstructing a broken cluster. \n This field - is read-only and no changes will be made by - Kubernetes to the PVC after it has been created. - \n Required, must not be nil." + description: |- + Will be used to create a stand-alone PVC to provision the volume. + The pod in which this EphemeralVolumeSource is embedded will be the + owner of the PVC, i.e. the PVC will be deleted together with the + pod. The name of the PVC will be `-` where + `` is the name from the `PodSpec.Volumes` array + entry. Pod validation will reject the pod if the concatenated name + is not valid for a PVC (for example, too long). + + + An existing PVC with that name that is not owned by the pod + will *not* be used for the pod to avoid using an unrelated + volume by mistake. Starting the pod is then blocked until + the unrelated PVC is removed. If such a pre-created PVC is + meant to be used by the pod, the PVC has to updated with an + owner reference to the pod once the pod exists. Normally + this should not be necessary, but it may be useful when + manually reconstructing a broken cluster. + + + This field is read-only and no changes will be made by Kubernetes + to the PVC after it has been created. + + + Required, must not be nil. properties: metadata: - description: May contain labels and annotations - that will be copied into the PVC when creating - it. No other fields are allowed and will - be rejected during validation. + description: |- + May contain labels and annotations that will be copied into the PVC + when creating it. No other fields are allowed and will be rejected during + validation. type: object spec: - description: The specification for the PersistentVolumeClaim. - The entire content is copied unchanged into - the PVC that gets created from this template. - The same fields as in a PersistentVolumeClaim + description: |- + The specification for the PersistentVolumeClaim. The entire content is + copied unchanged into the PVC that gets created from this + template. The same fields as in a PersistentVolumeClaim are also valid here. properties: accessModes: - description: 'AccessModes contains the - desired access modes the volume should - have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' + description: |- + accessModes contains the desired access modes the volume should have. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1 items: type: string type: array dataSource: - description: 'This field can be used to - specify either: * An existing VolumeSnapshot - object (snapshot.storage.k8s.io/VolumeSnapshot) + description: |- + dataSource field can be used to specify either: + * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot) * An existing PVC (PersistentVolumeClaim) - * An existing custom resource that implements - data population (Alpha) In order to - use custom resource types that implement - data population, the AnyVolumeDataSource - feature gate must be enabled. If the - provisioner or an external controller - can support the specified data source, - it will create a new volume based on - the contents of the specified data source.' + If the provisioner or an external controller can support the specified data source, + it will create a new volume based on the contents of the specified data source. + If the AnyVolumeDataSource feature gate is enabled, this field will always have + the same contents as the DataSourceRef field. properties: apiGroup: - description: APIGroup is the group - for the resource being referenced. - If APIGroup is not specified, the - specified Kind must be in the core - API group. For any other third-party - types, APIGroup is required. + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. type: string kind: description: Kind is the type of resource @@ -968,10 +986,53 @@ spec: - kind - name type: object + x-kubernetes-map-type: atomic + dataSourceRef: + description: |- + dataSourceRef specifies the object from which to populate the volume with data, if a non-empty + volume is desired. This may be any local object from a non-empty API group (non + core object) or a PersistentVolumeClaim object. + When this field is specified, volume binding will only succeed if the type of + the specified object matches some installed volume populator or dynamic + provisioner. + This field will replace the functionality of the DataSource field and as such + if both fields are non-empty, they must have the same value. For backwards + compatibility, both fields (DataSource and DataSourceRef) will be set to the same + value automatically if one of them is empty and the other is non-empty. + There are two important differences between DataSource and DataSourceRef: + * While DataSource only allows two specific types of objects, DataSourceRef + allows any non-core object, as well as PersistentVolumeClaim objects. + * While DataSource ignores disallowed values (dropping them), DataSourceRef + preserves all values, and generates an error if a disallowed value is + specified. + (Beta) Using this field requires the AnyVolumeDataSource feature gate to be enabled. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource + being referenced + type: string + name: + description: Name is the name of resource + being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic resources: - description: 'Resources represents the - minimum resources the volume should - have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources' + description: |- + resources represents the minimum resources the volume should have. + If RecoverVolumeExpansionFailure feature is enabled users are allowed to specify resource requirements + that are lower than previous value but must still be higher than capacity recorded in the + status field of the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources properties: limits: additionalProperties: @@ -980,9 +1041,9 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: 'Limits describes the - maximum amount of compute resources - allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -991,28 +1052,25 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: 'Requests describes the - minimum amount of compute resources - required. If Requests is omitted - for a container, it defaults to - Limits if that is explicitly specified, - otherwise to an implementation-defined - value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object selector: - description: A label query over volumes - to consider for binding. + description: selector is a label query + over volumes to consider for binding. properties: matchExpressions: description: matchExpressions is a list of label selector requirements. The requirements are ANDed. items: - description: A label selector requirement - is a selector that contains values, - a key, and an operator that relates - the key and values. + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. properties: key: description: key is the label @@ -1020,21 +1078,15 @@ spec: to. type: string operator: - description: operator represents - a key's relationship to a - set of values. Valid operators - are In, NotIn, Exists and - DoesNotExist. + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. type: string values: - description: values is an array - of string values. If the operator - is In or NotIn, the values - array must be non-empty. If - the operator is Exists or - DoesNotExist, the values array - must be empty. This array - is replaced during a strategic + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic merge patch. items: type: string @@ -1047,28 +1099,25 @@ spec: matchLabels: additionalProperties: type: string - description: matchLabels is a map - of {key,value} pairs. A single {key,value} - in the matchLabels map is equivalent - to an element of matchExpressions, - whose key field is "key", the operator - is "In", and the values array contains - only "value". The requirements are - ANDed. + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic storageClassName: - description: 'Name of the StorageClass - required by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' + description: |- + storageClassName is the name of the StorageClass required by the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1 type: string volumeMode: - description: volumeMode defines what type - of volume is required by the claim. - Value of Filesystem is implied when - not included in claim spec. + description: |- + volumeMode defines what type of volume is required by the claim. + Value of Filesystem is implied when not included in claim spec. type: string volumeName: - description: VolumeName is the binding + description: volumeName is the binding reference to the PersistentVolume backing this claim. type: string @@ -1078,277 +1127,281 @@ spec: type: object type: object fc: - description: FC represents a Fibre Channel resource + description: fc represents a Fibre Channel resource that is attached to a kubelet's host machine and then exposed to the pod. properties: fsType: - description: 'Filesystem type to mount. Must be - a filesystem type supported by the host operating - system. Ex. "ext4", "xfs", "ntfs". Implicitly - inferred to be "ext4" if unspecified. TODO: - how do we prevent errors in the filesystem from - compromising the machine' + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + TODO: how do we prevent errors in the filesystem from compromising the machine type: string lun: - description: 'Optional: FC target lun number' + description: 'lun is Optional: FC target lun number' format: int32 type: integer readOnly: - description: 'Optional: Defaults to false (read/write). - ReadOnly here will force the ReadOnly setting - in VolumeMounts.' + description: |- + readOnly is Optional: Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. type: boolean targetWWNs: - description: 'Optional: FC target worldwide names - (WWNs)' + description: 'targetWWNs is Optional: FC target + worldwide names (WWNs)' items: type: string type: array wwids: - description: 'Optional: FC volume world wide identifiers - (wwids) Either wwids or combination of targetWWNs - and lun must be set, but not both simultaneously.' + description: |- + wwids Optional: FC volume world wide identifiers (wwids) + Either wwids or combination of targetWWNs and lun must be set, but not both simultaneously. items: type: string type: array type: object flexVolume: - description: FlexVolume represents a generic volume - resource that is provisioned/attached using an exec - based plugin. + description: |- + flexVolume represents a generic volume resource that is + provisioned/attached using an exec based plugin. properties: driver: - description: Driver is the name of the driver + description: driver is the name of the driver to use for this volume. type: string fsType: - description: Filesystem type to mount. Must be - a filesystem type supported by the host operating - system. Ex. "ext4", "xfs", "ntfs". The default - filesystem depends on FlexVolume script. + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". The default filesystem depends on FlexVolume script. type: string options: additionalProperties: type: string - description: 'Optional: Extra command options - if any.' + description: 'options is Optional: this field + holds extra command options if any.' type: object readOnly: - description: 'Optional: Defaults to false (read/write). - ReadOnly here will force the ReadOnly setting - in VolumeMounts.' + description: |- + readOnly is Optional: defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. type: boolean secretRef: - description: 'Optional: SecretRef is reference - to the secret object containing sensitive information - to pass to the plugin scripts. This may be empty - if no secret object is specified. If the secret - object contains more than one secret, all secrets - are passed to the plugin scripts.' + description: |- + secretRef is Optional: secretRef is reference to the secret object containing + sensitive information to pass to the plugin scripts. This may be + empty if no secret object is specified. If the secret object + contains more than one secret, all secrets are passed to the plugin + scripts. properties: name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? type: string type: object + x-kubernetes-map-type: atomic required: - driver type: object flocker: - description: Flocker represents a Flocker volume attached + description: flocker represents a Flocker volume attached to a kubelet's host machine. This depends on the Flocker control service being running properties: datasetName: - description: Name of the dataset stored as metadata - -> name on the dataset for Flocker should be - considered as deprecated + description: |- + datasetName is Name of the dataset stored as metadata -> name on the dataset for Flocker + should be considered as deprecated type: string datasetUUID: - description: UUID of the dataset. This is unique - identifier of a Flocker dataset + description: datasetUUID is the UUID of the dataset. + This is unique identifier of a Flocker dataset type: string type: object gcePersistentDisk: - description: 'GCEPersistentDisk represents a GCE Disk - resource that is attached to a kubelet''s host machine - and then exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + description: |- + gcePersistentDisk represents a GCE Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk properties: fsType: - description: 'Filesystem type of the volume that - you want to mount. Tip: Ensure that the filesystem - type is supported by the host operating system. - Examples: "ext4", "xfs", "ntfs". Implicitly - inferred to be "ext4" if unspecified. More info: - https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk - TODO: how do we prevent errors in the filesystem - from compromising the machine' + description: |- + fsType is filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + TODO: how do we prevent errors in the filesystem from compromising the machine type: string partition: - description: 'The partition in the volume that - you want to mount. If omitted, the default is - to mount by volume name. Examples: For volume - /dev/sda1, you specify the partition as "1". - Similarly, the volume partition for /dev/sda - is "0" (or you can leave the property empty). - More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + description: |- + partition is the partition in the volume that you want to mount. + If omitted, the default is to mount by volume name. + Examples: For volume /dev/sda1, you specify the partition as "1". + Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty). + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk format: int32 type: integer pdName: - description: 'Unique name of the PD resource in - GCE. Used to identify the disk in GCE. More - info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + description: |- + pdName is unique name of the PD resource in GCE. Used to identify the disk in GCE. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk type: string readOnly: - description: 'ReadOnly here will force the ReadOnly - setting in VolumeMounts. Defaults to false. - More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk type: boolean required: - pdName type: object gitRepo: - description: 'GitRepo represents a git repository - at a particular revision. DEPRECATED: GitRepo is - deprecated. To provision a container with a git - repo, mount an EmptyDir into an InitContainer that - clones the repo using git, then mount the EmptyDir - into the Pod''s container.' + description: |- + gitRepo represents a git repository at a particular revision. + DEPRECATED: GitRepo is deprecated. To provision a container with a git repo, mount an + EmptyDir into an InitContainer that clones the repo using git, then mount the EmptyDir + into the Pod's container. properties: directory: - description: Target directory name. Must not contain - or start with '..'. If '.' is supplied, the - volume directory will be the git repository. Otherwise, - if specified, the volume will contain the git - repository in the subdirectory with the given - name. + description: |- + directory is the target directory name. + Must not contain or start with '..'. If '.' is supplied, the volume directory will be the + git repository. Otherwise, if specified, the volume will contain the git repository in + the subdirectory with the given name. type: string repository: - description: Repository URL + description: repository is the URL type: string revision: - description: Commit hash for the specified revision. + description: revision is the commit hash for the + specified revision. type: string required: - repository type: object glusterfs: - description: 'Glusterfs represents a Glusterfs mount - on the host that shares a pod''s lifetime. More - info: https://examples.k8s.io/volumes/glusterfs/README.md' + description: |- + glusterfs represents a Glusterfs mount on the host that shares a pod's lifetime. + More info: https://examples.k8s.io/volumes/glusterfs/README.md properties: endpoints: - description: 'EndpointsName is the endpoint name - that details Glusterfs topology. More info: - https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + description: |- + endpoints is the endpoint name that details Glusterfs topology. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod type: string path: - description: 'Path is the Glusterfs volume path. - More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + description: |- + path is the Glusterfs volume path. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod type: string readOnly: - description: 'ReadOnly here will force the Glusterfs - volume to be mounted with read-only permissions. - Defaults to false. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + description: |- + readOnly here will force the Glusterfs volume to be mounted with read-only permissions. + Defaults to false. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod type: boolean required: - endpoints - path type: object hostPath: - description: 'HostPath represents a pre-existing file - or directory on the host machine that is directly - exposed to the container. This is generally used - for system agents or other privileged things that - are allowed to see the host machine. Most containers - will NOT need this. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath - --- TODO(jonesdl) We need to restrict who can use - host directory mounts and who can/can not mount - host directories as read/write.' + description: |- + hostPath represents a pre-existing file or directory on the host + machine that is directly exposed to the container. This is generally + used for system agents or other privileged things that are allowed + to see the host machine. Most containers will NOT need this. + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + --- + TODO(jonesdl) We need to restrict who can use host directory mounts and who can/can not + mount host directories as read/write. properties: path: - description: 'Path of the directory on the host. - If the path is a symlink, it will follow the - link to the real path. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath' + description: |- + path of the directory on the host. + If the path is a symlink, it will follow the link to the real path. + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath type: string type: - description: 'Type for HostPath Volume Defaults - to "" More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath' + description: |- + type for HostPath Volume + Defaults to "" + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath type: string required: - path type: object iscsi: - description: 'ISCSI represents an ISCSI Disk resource - that is attached to a kubelet''s host machine and - then exposed to the pod. More info: https://examples.k8s.io/volumes/iscsi/README.md' + description: |- + iscsi represents an ISCSI Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + More info: https://examples.k8s.io/volumes/iscsi/README.md properties: chapAuthDiscovery: - description: whether support iSCSI Discovery CHAP - authentication + description: chapAuthDiscovery defines whether + support iSCSI Discovery CHAP authentication type: boolean chapAuthSession: - description: whether support iSCSI Session CHAP - authentication + description: chapAuthSession defines whether support + iSCSI Session CHAP authentication type: boolean fsType: - description: 'Filesystem type of the volume that - you want to mount. Tip: Ensure that the filesystem - type is supported by the host operating system. - Examples: "ext4", "xfs", "ntfs". Implicitly - inferred to be "ext4" if unspecified. More info: - https://kubernetes.io/docs/concepts/storage/volumes#iscsi - TODO: how do we prevent errors in the filesystem - from compromising the machine' + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi + TODO: how do we prevent errors in the filesystem from compromising the machine type: string initiatorName: - description: Custom iSCSI Initiator Name. If initiatorName - is specified with iscsiInterface simultaneously, - new iSCSI interface : will be created for the connection. + description: |- + initiatorName is the custom iSCSI Initiator Name. + If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface + : will be created for the connection. type: string iqn: - description: Target iSCSI Qualified Name. + description: iqn is the target iSCSI Qualified + Name. type: string iscsiInterface: - description: iSCSI Interface Name that uses an - iSCSI transport. Defaults to 'default' (tcp). + description: |- + iscsiInterface is the interface Name that uses an iSCSI transport. + Defaults to 'default' (tcp). type: string lun: - description: iSCSI Target Lun number. + description: lun represents iSCSI Target Lun number. format: int32 type: integer portals: - description: iSCSI Target Portal List. The portal - is either an IP or ip_addr:port if the port - is other than default (typically TCP ports 860 - and 3260). + description: |- + portals is the iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port + is other than default (typically TCP ports 860 and 3260). items: type: string type: array readOnly: - description: ReadOnly here will force the ReadOnly - setting in VolumeMounts. Defaults to false. + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. type: boolean secretRef: - description: CHAP Secret for iSCSI target and - initiator authentication + description: secretRef is the CHAP Secret for + iSCSI target and initiator authentication properties: name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? type: string type: object + x-kubernetes-map-type: atomic targetPortal: - description: iSCSI Target Portal. The Portal is - either an IP or ip_addr:port if the port is - other than default (typically TCP ports 860 - and 3260). + description: |- + targetPortal is iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port + is other than default (typically TCP ports 860 and 3260). type: string required: - iqn @@ -1356,159 +1409,152 @@ spec: - targetPortal type: object name: - description: 'Volume''s name. Must be a DNS_LABEL - and unique within the pod. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + description: |- + name of the volume. + Must be a DNS_LABEL and unique within the pod. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names type: string nfs: - description: 'NFS represents an NFS mount on the host - that shares a pod''s lifetime More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + description: |- + nfs represents an NFS mount on the host that shares a pod's lifetime + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs properties: path: - description: 'Path that is exported by the NFS - server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + description: |- + path that is exported by the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs type: string readOnly: - description: 'ReadOnly here will force the NFS - export to be mounted with read-only permissions. - Defaults to false. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + description: |- + readOnly here will force the NFS export to be mounted with read-only permissions. + Defaults to false. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs type: boolean server: - description: 'Server is the hostname or IP address - of the NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + description: |- + server is the hostname or IP address of the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs type: string required: - path - server type: object persistentVolumeClaim: - description: 'PersistentVolumeClaimVolumeSource represents - a reference to a PersistentVolumeClaim in the same - namespace. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' + description: |- + persistentVolumeClaimVolumeSource represents a reference to a + PersistentVolumeClaim in the same namespace. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims properties: claimName: - description: 'ClaimName is the name of a PersistentVolumeClaim - in the same namespace as the pod using this - volume. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' + description: |- + claimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims type: string readOnly: - description: Will force the ReadOnly setting in - VolumeMounts. Default false. + description: |- + readOnly Will force the ReadOnly setting in VolumeMounts. + Default false. type: boolean required: - claimName type: object photonPersistentDisk: - description: PhotonPersistentDisk represents a PhotonController + description: photonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine properties: fsType: - description: Filesystem type to mount. Must be - a filesystem type supported by the host operating - system. Ex. "ext4", "xfs", "ntfs". Implicitly - inferred to be "ext4" if unspecified. + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. type: string pdID: - description: ID that identifies Photon Controller - persistent disk + description: pdID is the ID that identifies Photon + Controller persistent disk type: string required: - pdID type: object portworxVolume: - description: PortworxVolume represents a portworx + description: portworxVolume represents a portworx volume attached and mounted on kubelets host machine properties: fsType: - description: FSType represents the filesystem - type to mount Must be a filesystem type supported - by the host operating system. Ex. "ext4", "xfs". - Implicitly inferred to be "ext4" if unspecified. + description: |- + fSType represents the filesystem type to mount + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs". Implicitly inferred to be "ext4" if unspecified. type: string readOnly: - description: Defaults to false (read/write). ReadOnly - here will force the ReadOnly setting in VolumeMounts. + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. type: boolean volumeID: - description: VolumeID uniquely identifies a Portworx + description: volumeID uniquely identifies a Portworx volume type: string required: - volumeID type: object projected: - description: Items for all in one resources secrets, - configmaps, and downward API + description: projected items for all in one resources + secrets, configmaps, and downward API properties: defaultMode: - description: Mode bits used to set permissions - on created files by default. Must be an octal - value between 0000 and 0777 or a decimal value - between 0 and 511. YAML accepts both octal and - decimal values, JSON requires decimal values - for mode bits. Directories within the path are - not affected by this setting. This might be - in conflict with other options that affect the - file mode, like fsGroup, and the result can - be other mode bits set. + description: |- + defaultMode are the mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. format: int32 type: integer sources: - description: list of volume projections + description: sources is the list of volume projections items: description: Projection that may be projected along with other supported volume types properties: configMap: - description: information about the configMap - data to project + description: configMap information about + the configMap data to project properties: items: - description: If unspecified, each key-value - pair in the Data field of the referenced - ConfigMap will be projected into the - volume as a file whose name is the - key and content is the value. If specified, - the listed keys will be projected - into the specified paths, and unlisted - keys will not be present. If a key - is specified which is not present - in the ConfigMap, the volume setup - will error unless it is marked optional. - Paths must be relative and may not - contain the '..' path or start with - '..'. + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. items: description: Maps a string key to a path within a volume. properties: key: - description: The key to project. + description: key is the key to + project. type: string mode: - description: 'Optional: mode bits - used to set permissions on this - file. Must be an octal value - between 0000 and 0777 or a decimal - value between 0 and 511. YAML - accepts both octal and decimal - values, JSON requires decimal - values for mode bits. If not - specified, the volume defaultMode - will be used. This might be - in conflict with other options - that affect the file mode, like - fsGroup, and the result can - be other mode bits set.' + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. format: int32 type: integer path: - description: The relative path - of the file to map the key to. + description: |- + path is the relative path of the file to map the key to. May not be an absolute path. - May not contain the path element - '..'. May not start with the - string '..'. + May not contain the path element '..'. + May not start with the string '..'. type: string required: - key @@ -1516,19 +1562,21 @@ spec: type: object type: array name: - description: 'Name of the referent. + description: |- + Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' + TODO: Add other useful fields. apiVersion, kind, uid? type: string optional: - description: Specify whether the ConfigMap - or its keys must be defined + description: optional specify whether + the ConfigMap or its keys must be + defined type: boolean type: object + x-kubernetes-map-type: atomic downwardAPI: - description: information about the downwardAPI - data to project + description: downwardAPI information about + the downwardAPI data to project properties: items: description: Items is a list of DownwardAPIVolume @@ -1558,21 +1606,15 @@ spec: required: - fieldPath type: object + x-kubernetes-map-type: atomic mode: - description: 'Optional: mode bits - used to set permissions on this - file, must be an octal value - between 0000 and 0777 or a decimal - value between 0 and 511. YAML - accepts both octal and decimal - values, JSON requires decimal - values for mode bits. If not - specified, the volume defaultMode - will be used. This might be - in conflict with other options - that affect the file mode, like - fsGroup, and the result can - be other mode bits set.' + description: |- + Optional: mode bits used to set permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. format: int32 type: integer path: @@ -1585,12 +1627,9 @@ spec: not start with ''..''' type: string resourceFieldRef: - description: 'Selects a resource - of the container: only resources - limits and requests (limits.cpu, - limits.memory, requests.cpu - and requests.memory) are currently - supported.' + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. properties: containerName: description: 'Container name: @@ -1613,61 +1652,49 @@ spec: required: - resource type: object + x-kubernetes-map-type: atomic required: - path type: object type: array type: object secret: - description: information about the secret - data to project + description: secret information about the + secret data to project properties: items: - description: If unspecified, each key-value - pair in the Data field of the referenced - Secret will be projected into the - volume as a file whose name is the - key and content is the value. If specified, - the listed keys will be projected - into the specified paths, and unlisted - keys will not be present. If a key - is specified which is not present - in the Secret, the volume setup will - error unless it is marked optional. - Paths must be relative and may not - contain the '..' path or start with - '..'. + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. items: description: Maps a string key to a path within a volume. properties: key: - description: The key to project. + description: key is the key to + project. type: string mode: - description: 'Optional: mode bits - used to set permissions on this - file. Must be an octal value - between 0000 and 0777 or a decimal - value between 0 and 511. YAML - accepts both octal and decimal - values, JSON requires decimal - values for mode bits. If not - specified, the volume defaultMode - will be used. This might be - in conflict with other options - that affect the file mode, like - fsGroup, and the result can - be other mode bits set.' + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. format: int32 type: integer path: - description: The relative path - of the file to map the key to. + description: |- + path is the relative path of the file to map the key to. May not be an absolute path. - May not contain the path element - '..'. May not start with the - string '..'. + May not contain the path element '..'. + May not start with the string '..'. type: string required: - key @@ -1675,47 +1702,44 @@ spec: type: object type: array name: - description: 'Name of the referent. + description: |- + Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' + TODO: Add other useful fields. apiVersion, kind, uid? type: string optional: - description: Specify whether the Secret - or its key must be defined + description: optional field specify + whether the Secret or its key must + be defined type: boolean type: object + x-kubernetes-map-type: atomic serviceAccountToken: - description: information about the serviceAccountToken - data to project + description: serviceAccountToken is information + about the serviceAccountToken data to + project properties: audience: - description: Audience is the intended - audience of the token. A recipient - of a token must identify itself with - an identifier specified in the audience - of the token, and otherwise should - reject the token. The audience defaults - to the identifier of the apiserver. + description: |- + audience is the intended audience of the token. A recipient of a token + must identify itself with an identifier specified in the audience of the + token, and otherwise should reject the token. The audience defaults to the + identifier of the apiserver. type: string expirationSeconds: - description: ExpirationSeconds is the - requested duration of validity of - the service account token. As the - token approaches expiration, the kubelet - volume plugin will proactively rotate - the service account token. The kubelet - will start trying to rotate the token - if the token is older than 80 percent - of its time to live or if the token - is older than 24 hours.Defaults to - 1 hour and must be at least 10 minutes. + description: |- + expirationSeconds is the requested duration of validity of the service + account token. As the token approaches expiration, the kubelet volume + plugin will proactively rotate the service account token. The kubelet will + start trying to rotate the token if the token is older than 80 percent of + its time to live or if the token is older than 24 hours.Defaults to 1 hour + and must be at least 10 minutes. format: int64 type: integer path: - description: Path is the path relative - to the mount point of the file to - project the token into. + description: |- + path is the path relative to the mount point of the file to project the + token into. type: string required: - path @@ -1724,36 +1748,37 @@ spec: type: array type: object quobyte: - description: Quobyte represents a Quobyte mount on + description: quobyte represents a Quobyte mount on the host that shares a pod's lifetime properties: group: - description: Group to map volume access to Default - is no group + description: |- + group to map volume access to + Default is no group type: string readOnly: - description: ReadOnly here will force the Quobyte - volume to be mounted with read-only permissions. + description: |- + readOnly here will force the Quobyte volume to be mounted with read-only permissions. Defaults to false. type: boolean registry: - description: Registry represents a single or multiple - Quobyte Registry services specified as a string - as host:port pair (multiple entries are separated - with commas) which acts as the central registry - for volumes + description: |- + registry represents a single or multiple Quobyte Registry services + specified as a string as host:port pair (multiple entries are separated with commas) + which acts as the central registry for volumes type: string tenant: - description: Tenant owning the given Quobyte volume - in the Backend Used with dynamically provisioned - Quobyte volumes, value is set by the plugin + description: |- + tenant owning the given Quobyte volume in the Backend + Used with dynamically provisioned Quobyte volumes, value is set by the plugin type: string user: - description: User to map volume access to Defaults - to serivceaccount user + description: |- + user to map volume access to + Defaults to serivceaccount user type: string volume: - description: Volume is a string that references + description: volume is a string that references an already created Quobyte volume by name. type: string required: @@ -1761,120 +1786,132 @@ spec: - volume type: object rbd: - description: 'RBD represents a Rados Block Device - mount on the host that shares a pod''s lifetime. - More info: https://examples.k8s.io/volumes/rbd/README.md' + description: |- + rbd represents a Rados Block Device mount on the host that shares a pod's lifetime. + More info: https://examples.k8s.io/volumes/rbd/README.md properties: fsType: - description: 'Filesystem type of the volume that - you want to mount. Tip: Ensure that the filesystem - type is supported by the host operating system. - Examples: "ext4", "xfs", "ntfs". Implicitly - inferred to be "ext4" if unspecified. More info: - https://kubernetes.io/docs/concepts/storage/volumes#rbd - TODO: how do we prevent errors in the filesystem - from compromising the machine' + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd + TODO: how do we prevent errors in the filesystem from compromising the machine type: string image: - description: 'The rados image name. More info: - https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + description: |- + image is the rados image name. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it type: string keyring: - description: 'Keyring is the path to key ring - for RBDUser. Default is /etc/ceph/keyring. More - info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + description: |- + keyring is the path to key ring for RBDUser. + Default is /etc/ceph/keyring. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it type: string monitors: - description: 'A collection of Ceph monitors. More - info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + description: |- + monitors is a collection of Ceph monitors. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it items: type: string type: array pool: - description: 'The rados pool name. Default is - rbd. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + description: |- + pool is the rados pool name. + Default is rbd. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it type: string readOnly: - description: 'ReadOnly here will force the ReadOnly - setting in VolumeMounts. Defaults to false. - More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it type: boolean secretRef: - description: 'SecretRef is name of the authentication - secret for RBDUser. If provided overrides keyring. - Default is nil. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + description: |- + secretRef is name of the authentication secret for RBDUser. If provided + overrides keyring. + Default is nil. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it properties: name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? type: string type: object + x-kubernetes-map-type: atomic user: - description: 'The rados user name. Default is - admin. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + description: |- + user is the rados user name. + Default is admin. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it type: string required: - image - monitors type: object scaleIO: - description: ScaleIO represents a ScaleIO persistent + description: scaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes. properties: fsType: - description: Filesystem type to mount. Must be - a filesystem type supported by the host operating - system. Ex. "ext4", "xfs", "ntfs". Default is - "xfs". + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". + Default is "xfs". type: string gateway: - description: The host address of the ScaleIO API - Gateway. + description: gateway is the host address of the + ScaleIO API Gateway. type: string protectionDomain: - description: The name of the ScaleIO Protection - Domain for the configured storage. + description: protectionDomain is the name of the + ScaleIO Protection Domain for the configured + storage. type: string readOnly: - description: Defaults to false (read/write). ReadOnly - here will force the ReadOnly setting in VolumeMounts. + description: |- + readOnly Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. type: boolean secretRef: - description: SecretRef references to the secret - for ScaleIO user and other sensitive information. - If this is not provided, Login operation will - fail. + description: |- + secretRef references to the secret for ScaleIO user and other + sensitive information. If this is not provided, Login operation will fail. properties: name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? type: string type: object + x-kubernetes-map-type: atomic sslEnabled: - description: Flag to enable/disable SSL communication - with Gateway, default false + description: sslEnabled Flag enable/disable SSL + communication with Gateway, default false type: boolean storageMode: - description: Indicates whether the storage for - a volume should be ThickProvisioned or ThinProvisioned. + description: |- + storageMode indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned. Default is ThinProvisioned. type: string storagePool: - description: The ScaleIO Storage Pool associated - with the protection domain. + description: storagePool is the ScaleIO Storage + Pool associated with the protection domain. type: string system: - description: The name of the storage system as - configured in ScaleIO. + description: system is the name of the storage + system as configured in ScaleIO. type: string volumeName: - description: The name of a volume already created - in the ScaleIO system that is associated with - this volume source. + description: |- + volumeName is the name of a volume already created in the ScaleIO system + that is associated with this volume source. type: string required: - gateway @@ -1882,61 +1919,53 @@ spec: - system type: object secret: - description: 'Secret represents a secret that should - populate this volume. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret' + description: |- + secret represents a secret that should populate this volume. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret properties: defaultMode: - description: 'Optional: mode bits used to set - permissions on created files by default. Must - be an octal value between 0000 and 0777 or a - decimal value between 0 and 511. YAML accepts - both octal and decimal values, JSON requires - decimal values for mode bits. Defaults to 0644. - Directories within the path are not affected - by this setting. This might be in conflict with - other options that affect the file mode, like - fsGroup, and the result can be other mode bits - set.' + description: |- + defaultMode is Optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values + for mode bits. Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. format: int32 type: integer items: - description: If unspecified, each key-value pair - in the Data field of the referenced Secret will - be projected into the volume as a file whose - name is the key and content is the value. If - specified, the listed keys will be projected - into the specified paths, and unlisted keys - will not be present. If a key is specified which - is not present in the Secret, the volume setup - will error unless it is marked optional. Paths - must be relative and may not contain the '..' - path or start with '..'. + description: |- + items If unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. items: description: Maps a string key to a path within a volume. properties: key: - description: The key to project. + description: key is the key to project. type: string mode: - description: 'Optional: mode bits used to - set permissions on this file. Must be - an octal value between 0000 and 0777 or - a decimal value between 0 and 511. YAML - accepts both octal and decimal values, - JSON requires decimal values for mode - bits. If not specified, the volume defaultMode - will be used. This might be in conflict - with other options that affect the file - mode, like fsGroup, and the result can - be other mode bits set.' + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. format: int32 type: integer path: - description: The relative path of the file - to map the key to. May not be an absolute - path. May not contain the path element - '..'. May not start with the string '..'. + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. type: string required: - key @@ -1944,79 +1973,80 @@ spec: type: object type: array optional: - description: Specify whether the Secret or its - keys must be defined + description: optional field specify whether the + Secret or its keys must be defined type: boolean secretName: - description: 'Name of the secret in the pod''s - namespace to use. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret' + description: |- + secretName is the name of the secret in the pod's namespace to use. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret type: string type: object storageos: - description: StorageOS represents a StorageOS volume + description: storageOS represents a StorageOS volume attached and mounted on Kubernetes nodes. properties: fsType: - description: Filesystem type to mount. Must be - a filesystem type supported by the host operating - system. Ex. "ext4", "xfs", "ntfs". Implicitly - inferred to be "ext4" if unspecified. + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. type: string readOnly: - description: Defaults to false (read/write). ReadOnly - here will force the ReadOnly setting in VolumeMounts. + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. type: boolean secretRef: - description: SecretRef specifies the secret to - use for obtaining the StorageOS API credentials. If - not specified, default values will be attempted. + description: |- + secretRef specifies the secret to use for obtaining the StorageOS API + credentials. If not specified, default values will be attempted. properties: name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? type: string type: object + x-kubernetes-map-type: atomic volumeName: - description: VolumeName is the human-readable - name of the StorageOS volume. Volume names - are only unique within a namespace. + description: |- + volumeName is the human-readable name of the StorageOS volume. Volume + names are only unique within a namespace. type: string volumeNamespace: - description: VolumeNamespace specifies the scope - of the volume within StorageOS. If no namespace - is specified then the Pod's namespace will be - used. This allows the Kubernetes name scoping - to be mirrored within StorageOS for tighter - integration. Set VolumeName to any name to override - the default behaviour. Set to "default" if you - are not using namespaces within StorageOS. Namespaces - that do not pre-exist within StorageOS will - be created. + description: |- + volumeNamespace specifies the scope of the volume within StorageOS. If no + namespace is specified then the Pod's namespace will be used. This allows the + Kubernetes name scoping to be mirrored within StorageOS for tighter integration. + Set VolumeName to any name to override the default behaviour. + Set to "default" if you are not using namespaces within StorageOS. + Namespaces that do not pre-exist within StorageOS will be created. type: string type: object vsphereVolume: - description: VsphereVolume represents a vSphere volume + description: vsphereVolume represents a vSphere volume attached and mounted on kubelets host machine properties: fsType: - description: Filesystem type to mount. Must be - a filesystem type supported by the host operating - system. Ex. "ext4", "xfs", "ntfs". Implicitly - inferred to be "ext4" if unspecified. + description: |- + fsType is filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. type: string storagePolicyID: - description: Storage Policy Based Management (SPBM) - profile ID associated with the StoragePolicyName. + description: storagePolicyID is the storage Policy + Based Management (SPBM) profile ID associated + with the StoragePolicyName. type: string storagePolicyName: - description: Storage Policy Based Management (SPBM) - profile name. + description: storagePolicyName is the storage + Policy Based Management (SPBM) profile name. type: string volumePath: - description: Path that identifies vSphere volume - vmdk + description: volumePath is the path that identifies + vSphere volume vmdk type: string required: - volumePath @@ -2026,24 +2056,21 @@ spec: type: object type: array type: object - supportStatus: - description: 'SupportStatus is used to indicate the support - status for services. Valid values are: - "continuous" (default): - operator is supported to be fresh deployed via OperandRequest - from scratch - "maintained" operator is not supported to be - fresh deployed via OperandRequest, only upgrade and deletion - are allowed' - type: string targetNamespaces: description: The target namespace of the OperatorGroups. items: type: string type: array + userManaged: + description: UserManaged is a flag to indicate whether operator + is managed by user + type: boolean required: - channel - name - packageName type: object + x-kubernetes-preserve-unknown-fields: true type: array type: object x-kubernetes-preserve-unknown-fields: true @@ -2054,8 +2081,9 @@ spec: description: Conditions represents the current state of the Request Service. items: - description: Condition represents the current state of the Request - Service. A condition might not show up if it is not happening. + description: |- + Condition represents the current state of the Request Service. + A condition might not show up if it is not happening. properties: lastTransitionTime: description: Last time the condition transitioned from one status @@ -2117,6 +2145,7 @@ spec: OperandRegistry. type: string type: object + x-kubernetes-preserve-unknown-fields: true type: object served: true storage: true @@ -2126,5 +2155,5 @@ status: acceptedNames: kind: "" plural: "" - conditions: [] - storedVersions: [] + conditions: null + storedVersions: null diff --git a/bundle/manifests/operator.ibm.com_operandrequests.yaml b/bundle/manifests/operator.ibm.com_operandrequests.yaml index f50a7623..8fc3373d 100644 --- a/bundle/manifests/operator.ibm.com_operandrequests.yaml +++ b/bundle/manifests/operator.ibm.com_operandrequests.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.1 + controller-gen.kubebuilder.io/version: v0.14.0 creationTimestamp: null labels: app.kubernetes.io/instance: operand-deployment-lifecycle-manager @@ -34,17 +34,24 @@ spec: name: v1alpha1 schema: openAPIV3Schema: - description: OperandRequest is the Schema for the operandrequests API. + description: OperandRequest is the Schema for the operandrequests API. Documentation + For additional details regarding install parameters check https://ibm.biz/icpfs39install. + License By installing this product you accept the license terms https://ibm.biz/icpfs39license properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -74,40 +81,79 @@ spec: type: string bindings: additionalProperties: - description: SecretConfigmap is a pair of Secret and/or - Configmap. + description: |- + Bindable is a Kubernetes resources to be shared from one namespace to another. + List of supported resources are Secrets, Configmaps, Services, and Routes. + Secrets and Configmaps will be copied such that a new Secret/Configmap with + exactly the same data will be created in the target namespace. + Services and Routes data will be copied into a configmap in the target + namespace. properties: configmap: description: The configmap identifies an existing configmap object. if it exists, the ODLM will share to the namespace of the OperandRequest. type: string + route: + description: |- + Route data will be shared by copying it into a configmap which is then + created in the target namespace + properties: + data: + additionalProperties: + type: string + description: |- + Data is a key-value pair where the value is a YAML path to a value in the + OpenShift Route, e.g. .spec.host or .spec.tls.termination + type: object + name: + description: Name is the name of the OpenShift + Route resource + type: string + type: object secret: description: The secret identifies an existing secret. if it exists, the ODLM will share to the namespace of the OperandRequest. type: string + service: + description: |- + Service data will be shared by copying it into a configmap which is then + created in the target namespace + properties: + data: + additionalProperties: + type: string + description: |- + Data is a key-value pair where the value is a YAML path to a value in the + Kubernetes Service, e.g. .spec.ports[0]port + type: object + name: + description: Name is the name of the Kubernetes + Service resource + type: string + type: object type: object description: The bindings section is used to specify names of secret and/or configmap. type: object instanceName: - description: InstanceName is used when users want to deploy - multiple custom resources. It is the name of the custom - resource. + description: |- + InstanceName is used when users want to deploy multiple custom resources. + It is the name of the custom resource. type: string kind: - description: Kind is used when users want to deploy multiple - custom resources. Kind identifies the kind of the custom - resource. + description: |- + Kind is used when users want to deploy multiple custom resources. + Kind identifies the kind of the custom resource. type: string name: description: Name of the operand to be deployed. type: string spec: - description: Spec is used when users want to deploy multiple - custom resources. It is the configuration map of custom - resource. + description: |- + Spec is used when users want to deploy multiple custom resources. + It is the configuration map of custom resource. nullable: true type: object x-kubernetes-preserve-unknown-fields: true @@ -120,9 +166,9 @@ spec: reside. type: string registryNamespace: - description: Specifies the namespace in which the OperandRegistry - reside. The default is the current namespace in which the - request is defined. + description: |- + Specifies the namespace in which the OperandRegistry reside. + The default is the current namespace in which the request is defined. type: string required: - operands @@ -140,8 +186,9 @@ spec: description: Conditions represents the current state of the Request Service. items: - description: Condition represents the current state of the Request - Service. A condition might not show up if it is not happening. + description: |- + Condition represents the current state of the Request Service. + A condition might not show up if it is not happening. properties: lastTransitionTime: description: Last time the condition transitioned from one status @@ -218,30 +265,25 @@ spec: description: Phase is the cluster running phase. type: string services: - description: Status for each of the services and their dependent resources specified in this request - type: array + description: Services reflect the status of operands beyond whether + they have been created items: - type: object properties: namespace: type: string operatorName: type: string resources: - description: Status of CRs created by ODLM that belong to a given service. This info comes from CRs like IM's Authentication and is populated by ODLM. - type: array + description: LastUpdateTime string `json:"lastTransitionTime,omitempty"` items: - type: object properties: apiVersion: type: string kind: type: string managedResources: - description: Status of resources that fall under the umbrella of the CR in the level above. - type: array + description: Message string `json:"message,omitempty"` items: - type: object properties: apiVersion: type: string @@ -252,15 +294,23 @@ spec: objectName: type: string status: + description: Type string `json:"type,omitempty"` type: string + type: object + type: array namespace: type: string objectName: type: string status: type: string + type: object + type: array status: + description: Type string `json:"type,omitempty"` type: string + type: object + type: array type: object x-kubernetes-preserve-unknown-fields: true type: object @@ -272,5 +322,5 @@ status: acceptedNames: kind: "" plural: "" - conditions: [] - storedVersions: [] + conditions: null + storedVersions: null diff --git a/bundle/manifests/operator.ibm.com_operatorconfigs.yaml b/bundle/manifests/operator.ibm.com_operatorconfigs.yaml new file mode 100644 index 00000000..cb6bdfb9 --- /dev/null +++ b/bundle/manifests/operator.ibm.com_operatorconfigs.yaml @@ -0,0 +1,1017 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + creationTimestamp: null + labels: + app.kubernetes.io/instance: operand-deployment-lifecycle-manager + app.kubernetes.io/managed-by: operand-deployment-lifecycle-manager + app.kubernetes.io/name: operand-deployment-lifecycle-manager + name: operatorconfigs.operator.ibm.com +spec: + group: operator.ibm.com + names: + kind: OperatorConfig + listKind: OperatorConfigList + plural: operatorconfigs + singular: operatorconfig + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - description: Current Phase + jsonPath: .status.phase + name: Phase + type: string + - jsonPath: .metadata.creationTimestamp + name: Created At + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: OperatorConfig is the Schema for the operatorconfigs API. Documentation + For additional details regarding install parameters check https://ibm.biz/icpfs39install. + License By installing this product you accept the license terms https://ibm.biz/icpfs39license + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: OperatorConfigSpec defines the desired state of OperatorConfig + properties: + foo: + description: Foo is an example field of OperatorConfig. Edit operatorconfig_types.go + to remove/update + type: string + services: + description: Services is a list of services to be configured, specifically + their operators + items: + description: ServiceOperatorConfig defines the configuration of + the service. + properties: + affinity: + description: If specified, the pod's scheduling constraints + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for + the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the + selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the + selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + x-kubernetes-map-type: atomic + weight: + description: Weight associated with matching the + corresponding nodeSelectorTerm, in the range + 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. + The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the + selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the + selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + x-kubernetes-map-type: atomic + type: array + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. + co-locate this pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of + label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of + label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules + (e.g. avoid putting this pod in the same node, zone, etc. + as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of + label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of + label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + name: + description: Name is the operator name as requested in the OperandRequest. + type: string + replicas: + description: |- + Number of desired pods. This is a pointer to distinguish between explicit + zero and not specified. Defaults to 1. + format: int32 + type: integer + topologySpreadConstraints: + description: |- + TopologySpreadConstraints describes how a group of pods ought to spread across topology + domains. Scheduler will schedule pods in a way which abides by the constraints. + All topologySpreadConstraints are ANDed. + items: + description: TopologySpreadConstraint specifies how to spread + matching pods among the given topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + maxSkew: + description: |- + MaxSkew describes the degree to which pods may be unevenly distributed. + When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference + between the number of matching pods in the target topology and the global minimum. + The global minimum is the minimum number of matching pods in an eligible domain + or zero if the number of eligible domains is less than MinDomains. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 2/2/1: + In this case, the global minimum is 1. + | zone1 | zone2 | zone3 | + | P P | P P | P | + - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; + scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) + violate MaxSkew(1). + - if MaxSkew is 2, incoming pod can be scheduled onto any zone. + When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence + to topologies that satisfy it. + It's a required field. Default value is 1 and 0 is not allowed. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + + + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + + + This is an alpha field and requires enabling MinDomainsInPodTopologySpread feature gate. + format: int32 + type: integer + topologyKey: + description: |- + TopologyKey is the key of node labels. Nodes that have a label with this key + and identical values are considered to be in the same topology. + We consider each as a "bucket", and try to put balanced number + of pods into each bucket. + We define a domain as a particular instance of a topology. + Also, we define an eligible domain as a domain whose nodes match the node selector. + e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. + And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. + It's a required field. + type: string + whenUnsatisfiable: + description: |- + WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy + the spread constraint. + - DoNotSchedule (default) tells the scheduler not to schedule it. + - ScheduleAnyway tells the scheduler to schedule the pod in any location, + but giving higher precedence to topologies that would help reduce the + skew. + A constraint is considered "Unsatisfiable" for an incoming pod + if and only if every possible node assignment for that pod would violate + "MaxSkew" on some topology. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 3/1/1: + | zone1 | zone2 | zone3 | + | P P P | P | P | + If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled + to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies + MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler + won't make it *more* imbalanced. + It's a required field. + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + x-kubernetes-list-map-keys: + - topologyKey + - whenUnsatisfiable + x-kubernetes-list-type: map + required: + - name + type: object + type: array + x-kubernetes-preserve-unknown-fields: true + type: object + x-kubernetes-preserve-unknown-fields: true + status: + description: OperatorConfigStatus defines the observed state of OperatorConfig + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/bundle/metadata/annotations.yaml b/bundle/metadata/annotations.yaml index f5b73652..10e9371e 100644 --- a/bundle/metadata/annotations.yaml +++ b/bundle/metadata/annotations.yaml @@ -4,12 +4,11 @@ annotations: operators.operatorframework.io.bundle.manifests.v1: manifests/ operators.operatorframework.io.bundle.metadata.v1: metadata/ operators.operatorframework.io.bundle.package.v1: ibm-odlm - operators.operatorframework.io.bundle.channels.v1: v4.0 - operators.operatorframework.io.bundle.channel.default.v1: v4.0 - operators.operatorframework.io.metrics.builder: operator-sdk-v1.24.0 + operators.operatorframework.io.bundle.channels.v1: v4.4 + operators.operatorframework.io.bundle.channel.default.v1: v4.4 + operators.operatorframework.io.metrics.builder: operator-sdk-v1.32.0 operators.operatorframework.io.metrics.mediatype.v1: metrics+v1 operators.operatorframework.io.metrics.project_layout: go.kubebuilder.io/v3 - # Annotations for testing. operators.operatorframework.io.test.mediatype.v1: scorecard+v1 operators.operatorframework.io.test.config.v1: tests/scorecard/ diff --git a/common/Makefile.common.mk b/common/Makefile.common.mk index e0e3d760..cea96080 100644 --- a/common/Makefile.common.mk +++ b/common/Makefile.common.mk @@ -21,23 +21,24 @@ PROJECT ?= oceanic-guard-191815 ZONE ?= us-east5-c CLUSTER ?= bedrock-prow -NAMESPACESCOPE_VERSION = 1.1.1 +NAMESPACESCOPE_VERSION = 1.17.3 OLM_API_VERSION = 0.3.8 activate-serviceaccount: ifdef GOOGLE_APPLICATION_CREDENTIALS - gcloud auth activate-service-account --key-file="$(GOOGLE_APPLICATION_CREDENTIALS)" + gcloud auth activate-service-account --key-file="$(GOOGLE_APPLICATION_CREDENTIALS)" || true endif get-cluster-credentials: activate-serviceaccount - gcloud container clusters get-credentials "$(CLUSTER)" --project="$(PROJECT)" --zone="$(ZONE)" + mkdir -p ~/.kube; cp -v /etc/kubeconfig/config ~/.kube; kubectl config use-context default; kubectl get nodes; echo going forward retiring google cloud + +ifdef GOOGLE_APPLICATION_CREDENTIALS + gcloud container clusters get-credentials "$(CLUSTER)" --project="$(PROJECT)" --zone="$(ZONE)" || true +endif config-docker: get-cluster-credentials @common/scripts/artifactory_config_docker.sh -config-docker-quay: get-cluster-credentials - @common/scripts/quay_config_docker.sh - # find or download operator-sdk # download operator-sdk if necessary operator-sdk: @@ -67,37 +68,36 @@ fetch-test-crds: rm -rf api-${OLM_API_VERSION} v${OLM_API_VERSION}.tar.gz ;\ } @{ \ - curl -L -O "https://github.com/horis233/jenkins-operator/archive/v0.3.3.tar.gz" ;\ - tar -zxf v0.3.3.tar.gz jenkins-operator-0.3.3/deploy/crds && mv jenkins-operator-0.3.3/deploy/crds/jenkins_v1alpha2_jenkins_crd.yaml ${ENVCRDS_DIR}/jenkins_v1alpha2_jenkins_crd.yaml ;\ - rm -rf jenkins-operator-0.3.3 v0.3.3.tar.gz ;\ + curl -L -O "https://github.com/mongodb/mongodb-atlas-kubernetes/archive/refs/tags/v1.7.3.tar.gz" ;\ + tar -zxf v1.7.3.tar.gz mongodb-atlas-kubernetes-1.7.3/deploy/crds && mv mongodb-atlas-kubernetes-1.7.3/deploy/crds/* ${ENVCRDS_DIR} ;\ + rm -rf mongodb-atlas-kubernetes-1.7.3 v1.7.3.tar.gz ;\ } @{ \ - curl -L -O "https://github.com/horis233/etcd-operator/archive/v0.9.4-crd.tar.gz" ;\ - tar -zxf v0.9.4-crd.tar.gz etcd-operator-0.9.4-crd/deploy/crds && mv etcd-operator-0.9.4-crd/deploy/crds/etcdclusters.etcd.database.coreos.com.crd.yaml ${ENVCRDS_DIR}/etcdclusters.etcd.database.coreos.com.crd.yaml ;\ - rm -rf etcd-operator-0.9.4-crd v0.9.4-crd.tar.gz ;\ + curl -L -O "https://github.com/jaegertracing/jaeger-operator/archive/refs/tags/v1.36.0.tar.gz" ;\ + tar -zxf v1.36.0.tar.gz jaeger-operator-1.36.0/bundle/manifests && mv jaeger-operator-1.36.0/bundle/manifests/jaegertracing.io_jaegers.yaml ${ENVCRDS_DIR}/jaegertracing.io_jaegers.yaml ;\ + rm -rf jaeger-operator-1.36.0 v1.36.0.tar.gz ;\ } @{ \ curl -L -O "https://github.com/IBM/ibm-namespace-scope-operator/archive/v${NAMESPACESCOPE_VERSION}.tar.gz" ;\ tar -zxf v${NAMESPACESCOPE_VERSION}.tar.gz ibm-namespace-scope-operator-${NAMESPACESCOPE_VERSION}/bundle/manifests && mv ibm-namespace-scope-operator-${NAMESPACESCOPE_VERSION}/bundle/manifests/operator.ibm.com_namespacescopes.yaml ${ENVCRDS_DIR}/operator.ibm.com_namespacescopes.yaml ;\ rm -rf ibm-namespace-scope-operator-${NAMESPACESCOPE_VERSION} v${NAMESPACESCOPE_VERSION}.tar.gz ;\ } + @{ \ + cp ./controllers/testutil/packagemanifests_crd.yaml ${ENVCRDS_DIR}/packagemanifests_crd.yaml ;\ + } CONTROLLER_GEN ?= $(shell pwd)/common/bin/controller-gen controller-gen: ## Download controller-gen locally if necessary. - $(call go-get-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.6.1) - -KUSTOMIZE ?= $(shell pwd)/common/bin/kustomize -kustomize: ## Download kustomize locally if necessary. - $(call go-get-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v4@v4.5.4) + $(call go-get-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.14.0) KIND ?= $(shell pwd)/common/bin/kind kind: ## Download kind locally if necessary. - $(call go-get-tool,$(KIND),sigs.k8s.io/kind@v0.10.0) + $(call go-get-tool,$(KIND),sigs.k8s.io/kind@v0.17.0) ENVTEST = $(shell pwd)/common/bin/setup-envtest setup-envtest: ## Download envtest-setup locally if necessary. - $(call go-get-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest@latest) + $(call go-get-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest@7b4325d5a38dff0c7eb9a939d079950eafcc4f7e) FINDFILES=find . \( -path ./.git -o -path ./.github -o -path ./testcrds \) -prune -o -type f XARGS = xargs -0 ${XARGS_FLAGS} diff --git a/common/config/.golangci.yml b/common/config/.golangci.yml index 342f8f8c..950c034b 100644 --- a/common/config/.golangci.yml +++ b/common/config/.golangci.yml @@ -1,6 +1,6 @@ service: # When updating this, also update the version stored in docker/build-tools/Dockerfile in the multicloudlab/tools repo. - golangci-lint-version: 1.18.x # use the fixed version to not introduce new linters unexpectedly + golangci-lint-version: 1.60.x # use the fixed version to not introduce new linters unexpectedly run: # timeout for analysis, e.g. 30s, 5m, default is 1m deadline: 20m @@ -10,43 +10,44 @@ run: # default value is empty list, but next dirs are always skipped independently # from this option's value: # vendor$, third_party$, testdata$, examples$, Godeps$, builtin$ - skip-dirs: - - genfiles$ - - vendor$ + # skip-dirs: + # - genfiles$ + # - vendor$ # which files to skip: they will be analyzed, but issues from them # won't be reported. Default value is empty list, but there is # no need to include all autogenerated files, we confidently recognize # autogenerated files. If it's not please let us know. - skip-files: - - ".*\\.pb\\.go" - - ".*\\.gen\\.go" + # skip-files: + # - ".*\\.pb\\.go" + # - ".*\\.gen\\.go" linters: # please, do not use `enable-all`: it's deprecated and will be removed soon. # inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint disable-all: true enable: - - deadcode + # - deadcode - errcheck # - gocyclo - gofmt - goimports - - golint + # - golint + - revive - gosec - gosimple - govet - ineffassign - - interfacer + # - interfacer - lll - misspell - staticcheck - - structcheck + # - structcheck - typecheck - unconvert - unparam - unused - - varcheck + # - varcheck # don't enable: # - gocritic # - bodyclose @@ -185,6 +186,15 @@ linters-settings: # - unnamedResult # - wrapperFunc + revive: + rules: + - name: dot-imports + arguments: + - allowedPackages: + - "github.com/onsi/ginkgo" + - "github.com/onsi/ginkgo/v2" + - "github.com/onsi/gomega" + issues: # List of regexps of issue texts to exclude, empty list by default. # But independently from this option we use default exclude patterns, @@ -199,6 +209,14 @@ issues: linters: - errcheck - maligned + + exclude-dirs: + - genfiles$ + - vendor$ + + exclude-files: + - ".*\\.pb\\.go" + - ".*\\.gen\\.go" # Independently from option `exclude` we use default exclude patterns, # it can be disabled by this option. To list all diff --git a/common/scripts/install-olm.sh b/common/scripts/install-olm.sh index 52f831fb..f5ccb3f0 100755 --- a/common/scripts/install-olm.sh +++ b/common/scripts/install-olm.sh @@ -19,31 +19,49 @@ set -e -if [[ ${#@} -ne 1 ]]; then - echo "Usage: $0 version" +default_base_url=https://github.com/operator-framework/operator-lifecycle-manager/releases/download + +if [[ ${#@} -lt 1 || ${#@} -gt 2 ]]; then + echo "Usage: $0 version [base_url]" echo "* version: the github release version" + echo "* base_url: the github base URL (Default: $default_base_url)" + exit 1 +fi + +if kubectl get deployment olm-operator -n openshift-operator-lifecycle-manager > /dev/null 2>&1; then + echo "OLM is already installed in a different configuration. This is common if you are not running a vanilla Kubernetes cluster. Exiting..." exit 1 fi -release=$1 -url=https://github.com/operator-framework/operator-lifecycle-manager/releases/download/${release} +release="$1" +base_url="${2:-${default_base_url}}" +url="${base_url}/${release}" namespace=olm -kubectl apply -f ${url}/crds.yaml -kubectl apply -f ${url}/olm.yaml +if kubectl get deployment olm-operator -n ${namespace} > /dev/null 2>&1; then + echo "OLM is already installed in ${namespace} namespace. Exiting..." + exit 1 +fi + +kubectl create -f "${url}/crds.yaml" +kubectl wait --for=condition=Established -f "${url}/crds.yaml" +kubectl create -f "${url}/olm.yaml" # wait for deployments to be ready kubectl rollout status -w deployment/olm-operator --namespace="${namespace}" kubectl rollout status -w deployment/catalog-operator --namespace="${namespace}" -retries=50 -until [[ $retries == 0 || $new_csv_phase == "Succeeded" ]]; do +retries=30 +until [[ $retries == 0 ]]; do new_csv_phase=$(kubectl get csv -n "${namespace}" packageserver -o jsonpath='{.status.phase}' 2>/dev/null || echo "Waiting for CSV to appear") if [[ $new_csv_phase != "$csv_phase" ]]; then csv_phase=$new_csv_phase echo "Package server phase: $csv_phase" fi - sleep 1 + if [[ "$new_csv_phase" == "Succeeded" ]]; then + break + fi + sleep 10 retries=$((retries - 1)) done diff --git a/common/scripts/lint_go.sh b/common/scripts/lint_go.sh index 84a70810..e3163d8f 100755 --- a/common/scripts/lint_go.sh +++ b/common/scripts/lint_go.sh @@ -15,4 +15,4 @@ # limitations under the License. # -GOGC=25 golangci-lint run -c ./common/config/.golangci.yml +GOGC=25 golangci-lint run -c ./common/config/.golangci.yml --timeout=300s diff --git a/common/scripts/next-csv.sh b/common/scripts/next-csv.sh index f0783e51..12279c24 100755 --- a/common/scripts/next-csv.sh +++ b/common/scripts/next-csv.sh @@ -35,6 +35,7 @@ if [[ "$OSTYPE" == "linux-gnu"* ]]; then # Update config/manifests/bases/operand-deployment-lifecycle-manager.clusterserviceversion.yaml sed -i "/olm.skipRange/s/$CURRENT_DEV_CSV/$NEW_DEV_CSV/g" config/manifests/bases/operand-deployment-lifecycle-manager.clusterserviceversion.yaml + sed -i "s/odlm:$CURRENT_DEV_CSV/odlm:$NEW_DEV_CSV/g" config/manifests/bases/operand-deployment-lifecycle-manager.clusterserviceversion.yaml echo "Updated the config/manifests/bases/operand-deployment-lifecycle-manager.clusterserviceversion.yaml" sed -i "s/OPERATOR_VERSION ?= $CURRENT_DEV_CSV/OPERATOR_VERSION ?= $NEW_DEV_CSV/g" Makefile @@ -54,6 +55,7 @@ elif [[ "$OSTYPE" == "darwin"* ]]; then # Update config/manifests/bases/operand-deployment-lifecycle-manager.clusterserviceversion.yaml sed -i "" "/olm.skipRange/s/$CURRENT_DEV_CSV/$NEW_DEV_CSV/g" config/manifests/bases/operand-deployment-lifecycle-manager.clusterserviceversion.yaml + sed -i "" "s/odlm:$CURRENT_DEV_CSV/odlm:$NEW_DEV_CSV/g" config/manifests/bases/operand-deployment-lifecycle-manager.clusterserviceversion.yaml echo "Updated the config/manifests/bases/operand-deployment-lifecycle-manager.clusterserviceversion.yaml" sed -i "" "s/OPERATOR_VERSION ?= $CURRENT_DEV_CSV/OPERATOR_VERSION ?= $NEW_DEV_CSV/g" Makefile diff --git a/common/scripts/quay_config_docker.sh b/common/scripts/quay_config_docker.sh deleted file mode 100755 index bea1ee9a..00000000 --- a/common/scripts/quay_config_docker.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash -# -# Copyright 2022 IBM Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -KUBECTL=$(command -v kubectl) -DOCKER_REGISTRY="quay.io" -DOCKER_USERNAME="multicloudlab" -DOCKER_PASSWORD=$(${KUBECTL} -n default get secret quay-cred -o jsonpath='{.data.password}' | base64 --decode) - -# support other container tools, e.g. podman -CONTAINER_CLI=${CONTAINER_CLI:-docker} - -# login the docker registry -${CONTAINER_CLI} login "${DOCKER_REGISTRY}" -u "${DOCKER_USERNAME}" -p "${DOCKER_PASSWORD}" - diff --git a/config/crd/bases/operator.ibm.com_operandbindinfos.yaml b/config/crd/bases/operator.ibm.com_operandbindinfos.yaml index 603d7116..8d39a398 100644 --- a/config/crd/bases/operator.ibm.com_operandbindinfos.yaml +++ b/config/crd/bases/operator.ibm.com_operandbindinfos.yaml @@ -1,11 +1,9 @@ - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.1 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.14.0 name: operandbindinfos.operator.ibm.com spec: group: operator.ibm.com @@ -33,16 +31,24 @@ spec: schema: openAPIV3Schema: description: OperandBindInfo is the Schema for the operandbindinfoes API. + Documentation For additional details regarding install parameters check + https://ibm.biz/icpfs39install. License By installing this product you accept + the license terms https://ibm.biz/icpfs39license properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -51,17 +57,56 @@ spec: properties: bindings: additionalProperties: - description: SecretConfigmap is a pair of Secret and/or Configmap. + description: |- + Bindable is a Kubernetes resources to be shared from one namespace to another. + List of supported resources are Secrets, Configmaps, Services, and Routes. + Secrets and Configmaps will be copied such that a new Secret/Configmap with + exactly the same data will be created in the target namespace. + Services and Routes data will be copied into a configmap in the target + namespace. properties: configmap: description: The configmap identifies an existing configmap object. if it exists, the ODLM will share to the namespace of the OperandRequest. type: string + route: + description: |- + Route data will be shared by copying it into a configmap which is then + created in the target namespace + properties: + data: + additionalProperties: + type: string + description: |- + Data is a key-value pair where the value is a YAML path to a value in the + OpenShift Route, e.g. .spec.host or .spec.tls.termination + type: object + name: + description: Name is the name of the OpenShift Route resource + type: string + type: object secret: description: The secret identifies an existing secret. if it exists, the ODLM will share to the namespace of the OperandRequest. type: string + service: + description: |- + Service data will be shared by copying it into a configmap which is then + created in the target namespace + properties: + data: + additionalProperties: + type: string + description: |- + Data is a key-value pair where the value is a YAML path to a value in the + Kubernetes Service, e.g. .spec.ports[0]port + type: object + name: + description: Name is the name of the Kubernetes Service + resource + type: string + type: object type: object description: The bindings section is used to specify information about the access/configuration data that is to be shared. @@ -69,7 +114,8 @@ spec: description: type: string operand: - description: The deployed service identifies itself with its operand. + description: |- + The deployed service identifies itself with its operand. This must match the name in the OperandRegistry in the current namespace. type: string registry: @@ -77,9 +123,9 @@ spec: CR from which this operand deployment is being requested. type: string registryNamespace: - description: Specifies the namespace in which the OperandRegistry - reside. The default is the current namespace in which the request - is defined. + description: |- + Specifies the namespace in which the OperandRegistry reside. + The default is the current namespace in which the request is defined. type: string required: - operand @@ -98,14 +144,9 @@ spec: type: string type: array type: object + x-kubernetes-preserve-unknown-fields: true type: object served: true storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/config/crd/bases/operator.ibm.com_operandconfigs.yaml b/config/crd/bases/operator.ibm.com_operandconfigs.yaml index ca778d1b..f17f5343 100644 --- a/config/crd/bases/operator.ibm.com_operandconfigs.yaml +++ b/config/crd/bases/operator.ibm.com_operandconfigs.yaml @@ -1,11 +1,9 @@ - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.1 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.14.0 name: operandconfigs.operator.ibm.com spec: group: operator.ibm.com @@ -32,17 +30,24 @@ spec: name: v1alpha1 schema: openAPIV3Schema: - description: OperandConfig is the Schema for the operandconfigs API. + description: OperandConfig is the Schema for the operandconfigs API. Documentation + For additional details regarding install parameters check https://ibm.biz/icpfs39install. + License By installing this product you accept the license terms https://ibm.biz/icpfs39license properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -100,11 +105,143 @@ spec: namespace: description: Namespace is the namespace of the resource. type: string + optionalFields: + description: OptionalFields is the list of fields that + could be updated additionally. + items: + description: OptionalField defines the optional field + for the resource. + properties: + matchExpressions: + description: MatchExpressions is the match expression + of the field. + items: + description: MatchExpression defines the match + expression of the field. + properties: + key: + description: Key is the key of the field. + type: string + objectRef: + description: ObjectRef is the reference of + the object. + properties: + apiVersion: + description: APIVersion is the version + of the object. + type: string + kind: + description: Kind is the kind of the object. + type: string + name: + description: Name is the name of the object. + type: string + namespace: + description: Namespace is the namespace + of the object. + type: string + required: + - apiVersion + - kind + - name + type: object + operator: + description: Operator is the operator of the + field. + type: string + values: + description: Values is the values of the field. + items: + type: string + type: array + required: + - key + - operator + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + operation: + description: Operation is the operation of the field. + type: string + path: + description: Path is the json path of the field. + type: string + valueFrom: + description: ValueFrom is the field value from the + object + properties: + objectRef: + description: ObjectRef is the reference of the + object. + properties: + apiVersion: + description: APIVersion is the version of + the object. + type: string + kind: + description: Kind is the kind of the object. + type: string + name: + description: Name is the name of the object. + type: string + namespace: + description: Namespace is the namespace + of the object. + type: string + required: + - apiVersion + - kind + - name + type: object + path: + description: Path is the json path of the field. + type: string + required: + - path + type: object + required: + - operation + - path + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + ownerReferences: + description: OwnerReferences is the list of owner references. + items: + properties: + apiVersion: + description: API version of the referent. + type: string + blockOwnerDeletion: + description: |- + If true, AND if the owner has the "foregroundDeletion" finalizer, then + the owner cannot be deleted from the key-value store until this + reference is removed. + Defaults to false. + type: boolean + controller: + description: |- + If true, this reference points to the managing controller. + Default is false. + type: boolean + kind: + description: Kind of the referent. + type: string + name: + description: Name of the referent. + type: string + required: + - apiVersion + - kind + - name + type: object + type: array required: - apiVersion - kind - name type: object + x-kubernetes-preserve-unknown-fields: true type: array spec: additionalProperties: @@ -141,14 +278,9 @@ spec: description: ServiceStatus defines all the status of a operator. type: object type: object + x-kubernetes-preserve-unknown-fields: true type: object served: true storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/config/crd/bases/operator.ibm.com_operandregistries.yaml b/config/crd/bases/operator.ibm.com_operandregistries.yaml index 592016e4..f6013aed 100644 --- a/config/crd/bases/operator.ibm.com_operandregistries.yaml +++ b/config/crd/bases/operator.ibm.com_operandregistries.yaml @@ -1,11 +1,9 @@ - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.1 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.14.0 name: operandregistries.operator.ibm.com spec: group: operator.ibm.com @@ -33,16 +31,24 @@ spec: schema: openAPIV3Schema: description: OperandRegistry is the Schema for the operandregistries API. + Documentation For additional details regarding install parameters check + https://ibm.biz/icpfs39install. License By installing this product you accept + the license terms https://ibm.biz/icpfs39license properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -60,36 +66,48 @@ spec: description: description: Description of a common service. type: string + fallbackChannels: + description: List of channels to fallback when the main channel + is not available. + items: + type: string + type: array installMode: - description: 'The install mode of an operator, either namespace - or cluster. Valid values are: - "namespace" (default): operator - is deployed in namespace of OperandRegistry; - "cluster": - operator is deployed in "openshift-operators" namespace;' + description: |- + The install mode of an operator, either namespace or cluster. + Valid values are: + - "namespace" (default): operator is deployed in namespace of OperandRegistry; + - "cluster": operator is deployed in "openshift-operators" namespace; + - "no-op": operator is not supported to be fresh deployed; type: string installPlanApproval: - description: 'Approval mode for emitted InstallPlans. Valid - values are: - "Automatic" (default): operator will be installed - automatically; - "Manual": operator installation will be pending - until users approve it;' + description: |- + Approval mode for emitted InstallPlans. + Valid values are: + - "Automatic" (default): operator will be installed automatically; + - "Manual": operator installation will be pending until users approve it; type: string name: description: A unique name for the operator whose operand may be deployed. type: string namespace: - description: The namespace in which operator should be deployed - when InstallMode is empty or set to "namespace". If the namespace - is not set, the operator namespace is the same as OperandRegistry - Namespace + description: |- + The namespace in which operator should be deployed when InstallMode is empty or set to "namespace". + If the namespace is not set, the operator namespace is the same as OperandRegistry Namespace + type: string + operatorConfig: + description: OperatorConfig is the name of the OperatorConfig type: string packageName: description: Name of the package that defines the applications. type: string scope: - description: 'A scope indicator, either public or private. Valid - values are: - "private" (default): deployment only request - from the containing names; - "public": deployment can be requested - from other namespaces;' + description: |- + A scope indicator, either public or private. + Valid values are: + - "private" (default): deployment only request from the containing names; + - "public": deployment can be requested from other namespaces; enum: - public - private @@ -110,8 +128,9 @@ spec: configuration. properties: env: - description: Env is a list of environment variables to set - in the container. Cannot be updated. + description: |- + Env is a list of environment variables to set in the container. + Cannot be updated. items: description: EnvVar represents an environment variable present in a Container. @@ -121,15 +140,16 @@ spec: be a C_IDENTIFIER. type: string value: - description: 'Variable references $(VAR_NAME) are - expanded using the previous defined environment - variables in the container and any service environment - variables. If a variable cannot be resolved, the - reference in the input string will be unchanged. - The $(VAR_NAME) syntax can be escaped with a double - $$, ie: $$(VAR_NAME). Escaped references will never - be expanded, regardless of whether the variable - exists or not. Defaults to "".' + description: |- + Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in the container and + any service environment variables. If a variable cannot be resolved, + the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless of whether the variable + exists or not. + Defaults to "". type: string valueFrom: description: Source for the environment variable's @@ -142,10 +162,10 @@ spec: description: The key to select. type: string name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? type: string optional: description: Specify whether the ConfigMap @@ -154,12 +174,11 @@ spec: required: - key type: object + x-kubernetes-map-type: atomic fieldRef: - description: 'Selects a field of the pod: supports - metadata.name, metadata.namespace, `metadata.labels['''']`, - `metadata.annotations['''']`, spec.nodeName, - spec.serviceAccountName, status.hostIP, status.podIP, - status.podIPs.' + description: |- + Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, + spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. properties: apiVersion: description: Version of the schema the FieldPath @@ -172,12 +191,11 @@ spec: required: - fieldPath type: object + x-kubernetes-map-type: atomic resourceFieldRef: - description: 'Selects a resource of the container: - only resources limits and requests (limits.cpu, - limits.memory, limits.ephemeral-storage, requests.cpu, - requests.memory and requests.ephemeral-storage) - are currently supported.' + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. properties: containerName: description: 'Container name: required for @@ -197,6 +215,7 @@ spec: required: - resource type: object + x-kubernetes-map-type: atomic secretKeyRef: description: Selects a key of a secret in the pod's namespace @@ -206,10 +225,10 @@ spec: from. Must be a valid secret key. type: string name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? type: string optional: description: Specify whether the Secret or @@ -218,19 +237,20 @@ spec: required: - key type: object + x-kubernetes-map-type: atomic type: object required: - name type: object type: array envFrom: - description: EnvFrom is a list of sources to populate environment - variables in the container. The keys defined within a - source must be a C_IDENTIFIER. All invalid keys will be - reported as an event when the container is starting. When - a key exists in multiple sources, the value associated - with the last source will take precedence. Values defined - by an Env with a duplicate key will take precedence. Immutable. + description: |- + EnvFrom is a list of sources to populate environment variables in the container. + The keys defined within a source must be a C_IDENTIFIER. All invalid keys + will be reported as an event when the container is starting. When a key exists in multiple + sources, the value associated with the last source will take precedence. + Values defined by an Env with a duplicate key will take precedence. + Immutable. items: description: EnvFromSource represents the source of a set of ConfigMaps @@ -239,16 +259,17 @@ spec: description: The ConfigMap to select from properties: name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? type: string optional: description: Specify whether the ConfigMap must be defined type: boolean type: object + x-kubernetes-map-type: atomic prefix: description: An optional identifier to prepend to each key in the ConfigMap. Must be a C_IDENTIFIER. @@ -257,29 +278,32 @@ spec: description: The Secret to select from properties: name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? type: string optional: description: Specify whether the Secret must be defined type: boolean type: object + x-kubernetes-map-type: atomic type: object type: array nodeSelector: additionalProperties: type: string - description: 'NodeSelector is a selector which must be true - for the pod to fit on a node. Selector which must match - a node''s labels for the pod to be scheduled on that node. - More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' + description: |- + NodeSelector is a selector which must be true for the pod to fit on a node. + Selector which must match a node's labels for the pod to be scheduled on that node. + More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ type: object resources: - description: 'Resources represents compute resources required - by this container. Immutable. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + description: |- + Resources represents compute resources required by this container. + Immutable. + More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/ properties: limits: additionalProperties: @@ -288,8 +312,9 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: 'Limits describes the maximum amount of - compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -298,25 +323,26 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: 'Requests describes the minimum amount - of compute resources required. If Requests is omitted - for a container, it defaults to Limits if that is - explicitly specified, otherwise to an implementation-defined - value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object selector: - description: Selector is the label selector for pods to - be configured. Existing ReplicaSets whose pods are selected - by this will be the ones affected by this deployment. + description: |- + Selector is the label selector for pods to be configured. + Existing ReplicaSets whose pods are + selected by this will be the ones affected by this deployment. It must match the pod template's labels. properties: matchExpressions: description: matchExpressions is a list of label selector requirements. The requirements are ANDed. items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. properties: key: @@ -324,17 +350,16 @@ spec: applies to. type: string operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists and DoesNotExist. + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. type: string values: - description: values is an array of string values. - If the operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be empty. - This array is replaced during a strategic merge - patch. + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. items: type: string type: array @@ -346,53 +371,49 @@ spec: matchLabels: additionalProperties: type: string - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field - is "key", the operator is "In", and the values array - contains only "value". The requirements are ANDed. + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic tolerations: description: Tolerations are the pod's tolerations. items: - description: The pod this Toleration is attached to tolerates - any taint that matches the triple - using the matching operator . + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . properties: effect: - description: Effect indicates the taint effect to - match. Empty means match all taint effects. When - specified, allowed values are NoSchedule, PreferNoSchedule - and NoExecute. + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. type: string key: - description: Key is the taint key that the toleration - applies to. Empty means match all taint keys. If - the key is empty, operator must be Exists; this - combination means to match all values and all keys. + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. type: string operator: - description: Operator represents a key's relationship - to the value. Valid operators are Exists and Equal. - Defaults to Equal. Exists is equivalent to wildcard - for value, so that a pod can tolerate all taints - of a particular category. + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. type: string tolerationSeconds: - description: TolerationSeconds represents the period - of time the toleration (which must be of effect - NoExecute, otherwise this field is ignored) tolerates - the taint. By default, it is not set, which means - tolerate the taint forever (do not evict). Zero - and negative values will be treated as 0 (evict - immediately) by the system. + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. format: int64 type: integer value: - description: Value is the taint value the toleration - matches to. If the operator is Exists, the value - should be empty, otherwise just a regular string. + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. type: string type: object type: array @@ -403,34 +424,36 @@ spec: within a container. properties: mountPath: - description: Path within the container at which the - volume should be mounted. Must not contain ':'. + description: |- + Path within the container at which the volume should be mounted. Must + not contain ':'. type: string mountPropagation: - description: mountPropagation determines how mounts - are propagated from the host to container and the - other way around. When not set, MountPropagationNone - is used. This field is beta in 1.10. + description: |- + mountPropagation determines how mounts are propagated from the host + to container and the other way around. + When not set, MountPropagationNone is used. + This field is beta in 1.10. type: string name: description: This must match the Name of a Volume. type: string readOnly: - description: Mounted read-only if true, read-write - otherwise (false or unspecified). Defaults to false. + description: |- + Mounted read-only if true, read-write otherwise (false or unspecified). + Defaults to false. type: boolean subPath: - description: Path within the volume from which the - container's volume should be mounted. Defaults to - "" (volume's root). + description: |- + Path within the volume from which the container's volume should be mounted. + Defaults to "" (volume's root). type: string subPathExpr: - description: Expanded path within the volume from - which the container's volume should be mounted. - Behaves similarly to SubPath but environment variable - references $(VAR_NAME) are expanded using the container's - environment. Defaults to "" (volume's root). SubPathExpr - and SubPath are mutually exclusive. + description: |- + Expanded path within the volume from which the container's volume should be mounted. + Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. + Defaults to "" (volume's root). + SubPathExpr and SubPath are mutually exclusive. type: string required: - mountPath @@ -444,234 +467,233 @@ spec: that may be accessed by any container in the pod. properties: awsElasticBlockStore: - description: 'AWSElasticBlockStore represents an AWS - Disk resource that is attached to a kubelet''s host - machine and then exposed to the pod. More info: - https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + description: |- + awsElasticBlockStore represents an AWS Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore properties: fsType: - description: 'Filesystem type of the volume that - you want to mount. Tip: Ensure that the filesystem - type is supported by the host operating system. - Examples: "ext4", "xfs", "ntfs". Implicitly - inferred to be "ext4" if unspecified. More info: - https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore - TODO: how do we prevent errors in the filesystem - from compromising the machine' + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + TODO: how do we prevent errors in the filesystem from compromising the machine type: string partition: - description: 'The partition in the volume that - you want to mount. If omitted, the default is - to mount by volume name. Examples: For volume - /dev/sda1, you specify the partition as "1". - Similarly, the volume partition for /dev/sda - is "0" (or you can leave the property empty).' + description: |- + partition is the partition in the volume that you want to mount. + If omitted, the default is to mount by volume name. + Examples: For volume /dev/sda1, you specify the partition as "1". + Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty). format: int32 type: integer readOnly: - description: 'Specify "true" to force and set - the ReadOnly property in VolumeMounts to "true". - If omitted, the default is "false". More info: - https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + description: |- + readOnly value true will force the readOnly setting in VolumeMounts. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore type: boolean volumeID: - description: 'Unique ID of the persistent disk - resource in AWS (Amazon EBS volume). More info: - https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + description: |- + volumeID is unique ID of the persistent disk resource in AWS (Amazon EBS volume). + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore type: string required: - volumeID type: object azureDisk: - description: AzureDisk represents an Azure Data Disk + description: azureDisk represents an Azure Data Disk mount on the host and bind mount to the pod. properties: cachingMode: - description: 'Host Caching mode: None, Read Only, - Read Write.' + description: 'cachingMode is the Host Caching + mode: None, Read Only, Read Write.' type: string diskName: - description: The Name of the data disk in the - blob storage + description: diskName is the Name of the data + disk in the blob storage type: string diskURI: - description: The URI the data disk in the blob - storage + description: diskURI is the URI of data disk in + the blob storage type: string fsType: - description: Filesystem type to mount. Must be - a filesystem type supported by the host operating - system. Ex. "ext4", "xfs", "ntfs". Implicitly - inferred to be "ext4" if unspecified. + description: |- + fsType is Filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. type: string kind: - description: 'Expected values Shared: multiple - blob disks per storage account Dedicated: single - blob disk per storage account Managed: azure - managed data disk (only in managed availability + description: 'kind expected values are Shared: + multiple blob disks per storage account Dedicated: + single blob disk per storage account Managed: + azure managed data disk (only in managed availability set). defaults to shared' type: string readOnly: - description: Defaults to false (read/write). ReadOnly - here will force the ReadOnly setting in VolumeMounts. + description: |- + readOnly Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. type: boolean required: - diskName - diskURI type: object azureFile: - description: AzureFile represents an Azure File Service + description: azureFile represents an Azure File Service mount on the host and bind mount to the pod. properties: readOnly: - description: Defaults to false (read/write). ReadOnly - here will force the ReadOnly setting in VolumeMounts. + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. type: boolean secretName: - description: the name of secret that contains - Azure Storage Account Name and Key + description: secretName is the name of secret + that contains Azure Storage Account Name and + Key type: string shareName: - description: Share Name + description: shareName is the azure share Name type: string required: - secretName - shareName type: object cephfs: - description: CephFS represents a Ceph FS mount on + description: cephFS represents a Ceph FS mount on the host that shares a pod's lifetime properties: monitors: - description: 'Required: Monitors is a collection - of Ceph monitors More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + description: |- + monitors is Required: Monitors is a collection of Ceph monitors + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it items: type: string type: array path: - description: 'Optional: Used as the mounted root, - rather than the full Ceph tree, default is /' + description: 'path is Optional: Used as the mounted + root, rather than the full Ceph tree, default + is /' type: string readOnly: - description: 'Optional: Defaults to false (read/write). - ReadOnly here will force the ReadOnly setting - in VolumeMounts. More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + description: |- + readOnly is Optional: Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it type: boolean secretFile: - description: 'Optional: SecretFile is the path - to key ring for User, default is /etc/ceph/user.secret - More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + description: |- + secretFile is Optional: SecretFile is the path to key ring for User, default is /etc/ceph/user.secret + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it type: string secretRef: - description: 'Optional: SecretRef is reference - to the authentication secret for User, default - is empty. More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + description: |- + secretRef is Optional: SecretRef is reference to the authentication secret for User, default is empty. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it properties: name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? type: string type: object + x-kubernetes-map-type: atomic user: - description: 'Optional: User is the rados user - name, default is admin More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + description: |- + user is optional: User is the rados user name, default is admin + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it type: string required: - monitors type: object cinder: - description: 'Cinder represents a cinder volume attached - and mounted on kubelets host machine. More info: - https://examples.k8s.io/mysql-cinder-pd/README.md' + description: |- + cinder represents a cinder volume attached and mounted on kubelets host machine. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md properties: fsType: - description: 'Filesystem type to mount. Must be - a filesystem type supported by the host operating - system. Examples: "ext4", "xfs", "ntfs". Implicitly - inferred to be "ext4" if unspecified. More info: - https://examples.k8s.io/mysql-cinder-pd/README.md' + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md type: string readOnly: - description: 'Optional: Defaults to false (read/write). - ReadOnly here will force the ReadOnly setting - in VolumeMounts. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md type: boolean secretRef: - description: 'Optional: points to a secret object - containing parameters used to connect to OpenStack.' + description: |- + secretRef is optional: points to a secret object containing parameters used to connect + to OpenStack. properties: name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? type: string type: object + x-kubernetes-map-type: atomic volumeID: - description: 'volume id used to identify the volume - in cinder. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + description: |- + volumeID used to identify the volume in cinder. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md type: string required: - volumeID type: object configMap: - description: ConfigMap represents a configMap that + description: configMap represents a configMap that should populate this volume properties: defaultMode: - description: 'Optional: mode bits used to set - permissions on created files by default. Must - be an octal value between 0000 and 0777 or a - decimal value between 0 and 511. YAML accepts - both octal and decimal values, JSON requires - decimal values for mode bits. Defaults to 0644. - Directories within the path are not affected - by this setting. This might be in conflict with - other options that affect the file mode, like - fsGroup, and the result can be other mode bits - set.' + description: |- + defaultMode is optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. format: int32 type: integer items: - description: If unspecified, each key-value pair - in the Data field of the referenced ConfigMap - will be projected into the volume as a file - whose name is the key and content is the value. - If specified, the listed keys will be projected - into the specified paths, and unlisted keys - will not be present. If a key is specified which - is not present in the ConfigMap, the volume - setup will error unless it is marked optional. - Paths must be relative and may not contain the - '..' path or start with '..'. + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. items: description: Maps a string key to a path within a volume. properties: key: - description: The key to project. + description: key is the key to project. type: string mode: - description: 'Optional: mode bits used to - set permissions on this file. Must be - an octal value between 0000 and 0777 or - a decimal value between 0 and 511. YAML - accepts both octal and decimal values, - JSON requires decimal values for mode - bits. If not specified, the volume defaultMode - will be used. This might be in conflict - with other options that affect the file - mode, like fsGroup, and the result can - be other mode bits set.' + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. format: int32 type: integer path: - description: The relative path of the file - to map the key to. May not be an absolute - path. May not contain the path element - '..'. May not start with the string '..'. + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. type: string required: - key @@ -679,82 +701,78 @@ spec: type: object type: array name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? type: string optional: - description: Specify whether the ConfigMap or - its keys must be defined + description: optional specify whether the ConfigMap + or its keys must be defined type: boolean type: object + x-kubernetes-map-type: atomic csi: - description: CSI (Container Storage Interface) represents + description: csi (Container Storage Interface) represents ephemeral storage that is handled by certain external CSI drivers (Beta feature). properties: driver: - description: Driver is the name of the CSI driver - that handles this volume. Consult with your - admin for the correct name as registered in - the cluster. + description: |- + driver is the name of the CSI driver that handles this volume. + Consult with your admin for the correct name as registered in the cluster. type: string fsType: - description: Filesystem type to mount. Ex. "ext4", - "xfs", "ntfs". If not provided, the empty value - is passed to the associated CSI driver which - will determine the default filesystem to apply. + description: |- + fsType to mount. Ex. "ext4", "xfs", "ntfs". + If not provided, the empty value is passed to the associated CSI driver + which will determine the default filesystem to apply. type: string nodePublishSecretRef: - description: NodePublishSecretRef is a reference - to the secret object containing sensitive information - to pass to the CSI driver to complete the CSI + description: |- + nodePublishSecretRef is a reference to the secret object containing + sensitive information to pass to the CSI driver to complete the CSI NodePublishVolume and NodeUnpublishVolume calls. - This field is optional, and may be empty if - no secret is required. If the secret object - contains more than one secret, all secret references - are passed. + This field is optional, and may be empty if no secret is required. If the + secret object contains more than one secret, all secret references are passed. properties: name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? type: string type: object + x-kubernetes-map-type: atomic readOnly: - description: Specifies a read-only configuration - for the volume. Defaults to false (read/write). + description: |- + readOnly specifies a read-only configuration for the volume. + Defaults to false (read/write). type: boolean volumeAttributes: additionalProperties: type: string - description: VolumeAttributes stores driver-specific - properties that are passed to the CSI driver. - Consult your driver's documentation for supported - values. + description: |- + volumeAttributes stores driver-specific properties that are passed to the CSI + driver. Consult your driver's documentation for supported values. type: object required: - driver type: object downwardAPI: - description: DownwardAPI represents downward API about + description: downwardAPI represents downward API about the pod that should populate this volume properties: defaultMode: - description: 'Optional: mode bits to use on created - files by default. Must be a Optional: mode bits - used to set permissions on created files by - default. Must be an octal value between 0000 - and 0777 or a decimal value between 0 and 511. - YAML accepts both octal and decimal values, - JSON requires decimal values for mode bits. - Defaults to 0644. Directories within the path - are not affected by this setting. This might - be in conflict with other options that affect - the file mode, like fsGroup, and the result - can be other mode bits set.' + description: |- + Optional: mode bits to use on created files by default. Must be a + Optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. format: int32 type: integer items: @@ -782,18 +800,15 @@ spec: required: - fieldPath type: object + x-kubernetes-map-type: atomic mode: - description: 'Optional: mode bits used to - set permissions on this file, must be - an octal value between 0000 and 0777 or - a decimal value between 0 and 511. YAML - accepts both octal and decimal values, - JSON requires decimal values for mode - bits. If not specified, the volume defaultMode - will be used. This might be in conflict - with other options that affect the file - mode, like fsGroup, and the result can - be other mode bits set.' + description: |- + Optional: mode bits used to set permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. format: int32 type: integer path: @@ -805,10 +820,9 @@ spec: with ''..''' type: string resourceFieldRef: - description: 'Selects a resource of the - container: only resources limits and requests - (limits.cpu, limits.memory, requests.cpu - and requests.memory) are currently supported.' + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. properties: containerName: description: 'Container name: required @@ -830,129 +844,170 @@ spec: required: - resource type: object + x-kubernetes-map-type: atomic required: - path type: object type: array type: object emptyDir: - description: 'EmptyDir represents a temporary directory - that shares a pod''s lifetime. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' + description: |- + emptyDir represents a temporary directory that shares a pod's lifetime. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir properties: medium: - description: 'What type of storage medium should - back this directory. The default is "" which - means to use the node''s default medium. Must - be an empty string (default) or Memory. More - info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' + description: |- + medium represents what type of storage medium should back this directory. + The default is "" which means to use the node's default medium. + Must be an empty string (default) or Memory. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir type: string sizeLimit: anyOf: - type: integer - type: string - description: 'Total amount of local storage required - for this EmptyDir volume. The size limit is - also applicable for memory medium. The maximum - usage on memory medium EmptyDir would be the - minimum value between the SizeLimit specified - here and the sum of memory limits of all containers - in a pod. The default is nil which means that - the limit is undefined. More info: http://kubernetes.io/docs/user-guide/volumes#emptydir' + description: |- + sizeLimit is the total amount of local storage required for this EmptyDir volume. + The size limit is also applicable for memory medium. + The maximum usage on memory medium EmptyDir would be the minimum value between + the SizeLimit specified here and the sum of memory limits of all containers in a pod. + The default is nil which means that the limit is undefined. + More info: http://kubernetes.io/docs/user-guide/volumes#emptydir pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true type: object ephemeral: - description: "Ephemeral represents a volume that is - handled by a cluster storage driver. The volume's - lifecycle is tied to the pod that defines it - it - will be created before the pod starts, and deleted - when the pod is removed. \n Use this if: a) the - volume is only needed while the pod runs, b) features - of normal volumes like restoring from snapshot or - capacity tracking are needed, c) the storage - driver is specified through a storage class, and - d) the storage driver supports dynamic volume provisioning - through a PersistentVolumeClaim (see EphemeralVolumeSource - for more information on the connection between - this volume type and PersistentVolumeClaim). - \n Use PersistentVolumeClaim or one of the vendor-specific - APIs for volumes that persist for longer than the - lifecycle of an individual pod. \n Use CSI for light-weight - local ephemeral volumes if the CSI driver is meant - to be used that way - see the documentation of the - driver for more information. \n A pod can use both - types of ephemeral volumes and persistent volumes - at the same time. \n This is a beta feature and - only available when the GenericEphemeralVolume feature - gate is enabled." + description: |- + ephemeral represents a volume that is handled by a cluster storage driver. + The volume's lifecycle is tied to the pod that defines it - it will be created before the pod starts, + and deleted when the pod is removed. + + + Use this if: + a) the volume is only needed while the pod runs, + b) features of normal volumes like restoring from snapshot or capacity + tracking are needed, + c) the storage driver is specified through a storage class, and + d) the storage driver supports dynamic volume provisioning through + a PersistentVolumeClaim (see EphemeralVolumeSource for more + information on the connection between this volume type + and PersistentVolumeClaim). + + + Use PersistentVolumeClaim or one of the vendor-specific + APIs for volumes that persist for longer than the lifecycle + of an individual pod. + + + Use CSI for light-weight local ephemeral volumes if the CSI driver is meant to + be used that way - see the documentation of the driver for + more information. + + + A pod can use both types of ephemeral volumes and + persistent volumes at the same time. properties: volumeClaimTemplate: - description: "Will be used to create a stand-alone - PVC to provision the volume. The pod in which - this EphemeralVolumeSource is embedded will - be the owner of the PVC, i.e. the PVC will be - deleted together with the pod. The name of - the PVC will be `-` where - `` is the name from the `PodSpec.Volumes` - array entry. Pod validation will reject the - pod if the concatenated name is not valid for - a PVC (for example, too long). \n An existing - PVC with that name that is not owned by the - pod will *not* be used for the pod to avoid - using an unrelated volume by mistake. Starting - the pod is then blocked until the unrelated - PVC is removed. If such a pre-created PVC is - meant to be used by the pod, the PVC has to - updated with an owner reference to the pod once - the pod exists. Normally this should not be - necessary, but it may be useful when manually - reconstructing a broken cluster. \n This field - is read-only and no changes will be made by - Kubernetes to the PVC after it has been created. - \n Required, must not be nil." + description: |- + Will be used to create a stand-alone PVC to provision the volume. + The pod in which this EphemeralVolumeSource is embedded will be the + owner of the PVC, i.e. the PVC will be deleted together with the + pod. The name of the PVC will be `-` where + `` is the name from the `PodSpec.Volumes` array + entry. Pod validation will reject the pod if the concatenated name + is not valid for a PVC (for example, too long). + + + An existing PVC with that name that is not owned by the pod + will *not* be used for the pod to avoid using an unrelated + volume by mistake. Starting the pod is then blocked until + the unrelated PVC is removed. If such a pre-created PVC is + meant to be used by the pod, the PVC has to updated with an + owner reference to the pod once the pod exists. Normally + this should not be necessary, but it may be useful when + manually reconstructing a broken cluster. + + + This field is read-only and no changes will be made by Kubernetes + to the PVC after it has been created. + + + Required, must not be nil. properties: metadata: - description: May contain labels and annotations - that will be copied into the PVC when creating - it. No other fields are allowed and will - be rejected during validation. + description: |- + May contain labels and annotations that will be copied into the PVC + when creating it. No other fields are allowed and will be rejected during + validation. type: object spec: - description: The specification for the PersistentVolumeClaim. - The entire content is copied unchanged into - the PVC that gets created from this template. - The same fields as in a PersistentVolumeClaim + description: |- + The specification for the PersistentVolumeClaim. The entire content is + copied unchanged into the PVC that gets created from this + template. The same fields as in a PersistentVolumeClaim are also valid here. properties: accessModes: - description: 'AccessModes contains the - desired access modes the volume should - have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' + description: |- + accessModes contains the desired access modes the volume should have. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1 items: type: string type: array dataSource: - description: 'This field can be used to - specify either: * An existing VolumeSnapshot - object (snapshot.storage.k8s.io/VolumeSnapshot) + description: |- + dataSource field can be used to specify either: + * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot) * An existing PVC (PersistentVolumeClaim) - * An existing custom resource that implements - data population (Alpha) In order to - use custom resource types that implement - data population, the AnyVolumeDataSource - feature gate must be enabled. If the - provisioner or an external controller - can support the specified data source, - it will create a new volume based on - the contents of the specified data source.' + If the provisioner or an external controller can support the specified data source, + it will create a new volume based on the contents of the specified data source. + If the AnyVolumeDataSource feature gate is enabled, this field will always have + the same contents as the DataSourceRef field. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource + being referenced + type: string + name: + description: Name is the name of resource + being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + dataSourceRef: + description: |- + dataSourceRef specifies the object from which to populate the volume with data, if a non-empty + volume is desired. This may be any local object from a non-empty API group (non + core object) or a PersistentVolumeClaim object. + When this field is specified, volume binding will only succeed if the type of + the specified object matches some installed volume populator or dynamic + provisioner. + This field will replace the functionality of the DataSource field and as such + if both fields are non-empty, they must have the same value. For backwards + compatibility, both fields (DataSource and DataSourceRef) will be set to the same + value automatically if one of them is empty and the other is non-empty. + There are two important differences between DataSource and DataSourceRef: + * While DataSource only allows two specific types of objects, DataSourceRef + allows any non-core object, as well as PersistentVolumeClaim objects. + * While DataSource ignores disallowed values (dropping them), DataSourceRef + preserves all values, and generates an error if a disallowed value is + specified. + (Beta) Using this field requires the AnyVolumeDataSource feature gate to be enabled. properties: apiGroup: - description: APIGroup is the group - for the resource being referenced. - If APIGroup is not specified, the - specified Kind must be in the core - API group. For any other third-party - types, APIGroup is required. + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. type: string kind: description: Kind is the type of resource @@ -966,10 +1021,14 @@ spec: - kind - name type: object + x-kubernetes-map-type: atomic resources: - description: 'Resources represents the - minimum resources the volume should - have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources' + description: |- + resources represents the minimum resources the volume should have. + If RecoverVolumeExpansionFailure feature is enabled users are allowed to specify resource requirements + that are lower than previous value but must still be higher than capacity recorded in the + status field of the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources properties: limits: additionalProperties: @@ -978,9 +1037,9 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: 'Limits describes the - maximum amount of compute resources - allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -989,28 +1048,25 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: 'Requests describes the - minimum amount of compute resources - required. If Requests is omitted - for a container, it defaults to - Limits if that is explicitly specified, - otherwise to an implementation-defined - value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object selector: - description: A label query over volumes - to consider for binding. + description: selector is a label query + over volumes to consider for binding. properties: matchExpressions: description: matchExpressions is a list of label selector requirements. The requirements are ANDed. items: - description: A label selector requirement - is a selector that contains values, - a key, and an operator that relates - the key and values. + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. properties: key: description: key is the label @@ -1018,21 +1074,15 @@ spec: to. type: string operator: - description: operator represents - a key's relationship to a - set of values. Valid operators - are In, NotIn, Exists and - DoesNotExist. + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. type: string values: - description: values is an array - of string values. If the operator - is In or NotIn, the values - array must be non-empty. If - the operator is Exists or - DoesNotExist, the values array - must be empty. This array - is replaced during a strategic + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic merge patch. items: type: string @@ -1045,28 +1095,25 @@ spec: matchLabels: additionalProperties: type: string - description: matchLabels is a map - of {key,value} pairs. A single {key,value} - in the matchLabels map is equivalent - to an element of matchExpressions, - whose key field is "key", the operator - is "In", and the values array contains - only "value". The requirements are - ANDed. + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic storageClassName: - description: 'Name of the StorageClass - required by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' + description: |- + storageClassName is the name of the StorageClass required by the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1 type: string volumeMode: - description: volumeMode defines what type - of volume is required by the claim. - Value of Filesystem is implied when - not included in claim spec. + description: |- + volumeMode defines what type of volume is required by the claim. + Value of Filesystem is implied when not included in claim spec. type: string volumeName: - description: VolumeName is the binding + description: volumeName is the binding reference to the PersistentVolume backing this claim. type: string @@ -1076,277 +1123,281 @@ spec: type: object type: object fc: - description: FC represents a Fibre Channel resource + description: fc represents a Fibre Channel resource that is attached to a kubelet's host machine and then exposed to the pod. properties: fsType: - description: 'Filesystem type to mount. Must be - a filesystem type supported by the host operating - system. Ex. "ext4", "xfs", "ntfs". Implicitly - inferred to be "ext4" if unspecified. TODO: - how do we prevent errors in the filesystem from - compromising the machine' + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + TODO: how do we prevent errors in the filesystem from compromising the machine type: string lun: - description: 'Optional: FC target lun number' + description: 'lun is Optional: FC target lun number' format: int32 type: integer readOnly: - description: 'Optional: Defaults to false (read/write). - ReadOnly here will force the ReadOnly setting - in VolumeMounts.' + description: |- + readOnly is Optional: Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. type: boolean targetWWNs: - description: 'Optional: FC target worldwide names - (WWNs)' + description: 'targetWWNs is Optional: FC target + worldwide names (WWNs)' items: type: string type: array wwids: - description: 'Optional: FC volume world wide identifiers - (wwids) Either wwids or combination of targetWWNs - and lun must be set, but not both simultaneously.' + description: |- + wwids Optional: FC volume world wide identifiers (wwids) + Either wwids or combination of targetWWNs and lun must be set, but not both simultaneously. items: type: string type: array type: object flexVolume: - description: FlexVolume represents a generic volume - resource that is provisioned/attached using an exec - based plugin. + description: |- + flexVolume represents a generic volume resource that is + provisioned/attached using an exec based plugin. properties: driver: - description: Driver is the name of the driver + description: driver is the name of the driver to use for this volume. type: string fsType: - description: Filesystem type to mount. Must be - a filesystem type supported by the host operating - system. Ex. "ext4", "xfs", "ntfs". The default - filesystem depends on FlexVolume script. + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". The default filesystem depends on FlexVolume script. type: string options: additionalProperties: type: string - description: 'Optional: Extra command options - if any.' + description: 'options is Optional: this field + holds extra command options if any.' type: object readOnly: - description: 'Optional: Defaults to false (read/write). - ReadOnly here will force the ReadOnly setting - in VolumeMounts.' + description: |- + readOnly is Optional: defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. type: boolean secretRef: - description: 'Optional: SecretRef is reference - to the secret object containing sensitive information - to pass to the plugin scripts. This may be empty - if no secret object is specified. If the secret - object contains more than one secret, all secrets - are passed to the plugin scripts.' + description: |- + secretRef is Optional: secretRef is reference to the secret object containing + sensitive information to pass to the plugin scripts. This may be + empty if no secret object is specified. If the secret object + contains more than one secret, all secrets are passed to the plugin + scripts. properties: name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? type: string type: object + x-kubernetes-map-type: atomic required: - driver type: object flocker: - description: Flocker represents a Flocker volume attached + description: flocker represents a Flocker volume attached to a kubelet's host machine. This depends on the Flocker control service being running properties: datasetName: - description: Name of the dataset stored as metadata - -> name on the dataset for Flocker should be - considered as deprecated + description: |- + datasetName is Name of the dataset stored as metadata -> name on the dataset for Flocker + should be considered as deprecated type: string datasetUUID: - description: UUID of the dataset. This is unique - identifier of a Flocker dataset + description: datasetUUID is the UUID of the dataset. + This is unique identifier of a Flocker dataset type: string type: object gcePersistentDisk: - description: 'GCEPersistentDisk represents a GCE Disk - resource that is attached to a kubelet''s host machine - and then exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + description: |- + gcePersistentDisk represents a GCE Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk properties: fsType: - description: 'Filesystem type of the volume that - you want to mount. Tip: Ensure that the filesystem - type is supported by the host operating system. - Examples: "ext4", "xfs", "ntfs". Implicitly - inferred to be "ext4" if unspecified. More info: - https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk - TODO: how do we prevent errors in the filesystem - from compromising the machine' + description: |- + fsType is filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + TODO: how do we prevent errors in the filesystem from compromising the machine type: string partition: - description: 'The partition in the volume that - you want to mount. If omitted, the default is - to mount by volume name. Examples: For volume - /dev/sda1, you specify the partition as "1". - Similarly, the volume partition for /dev/sda - is "0" (or you can leave the property empty). - More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + description: |- + partition is the partition in the volume that you want to mount. + If omitted, the default is to mount by volume name. + Examples: For volume /dev/sda1, you specify the partition as "1". + Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty). + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk format: int32 type: integer pdName: - description: 'Unique name of the PD resource in - GCE. Used to identify the disk in GCE. More - info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + description: |- + pdName is unique name of the PD resource in GCE. Used to identify the disk in GCE. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk type: string readOnly: - description: 'ReadOnly here will force the ReadOnly - setting in VolumeMounts. Defaults to false. - More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk type: boolean required: - pdName type: object gitRepo: - description: 'GitRepo represents a git repository - at a particular revision. DEPRECATED: GitRepo is - deprecated. To provision a container with a git - repo, mount an EmptyDir into an InitContainer that - clones the repo using git, then mount the EmptyDir - into the Pod''s container.' + description: |- + gitRepo represents a git repository at a particular revision. + DEPRECATED: GitRepo is deprecated. To provision a container with a git repo, mount an + EmptyDir into an InitContainer that clones the repo using git, then mount the EmptyDir + into the Pod's container. properties: directory: - description: Target directory name. Must not contain - or start with '..'. If '.' is supplied, the - volume directory will be the git repository. Otherwise, - if specified, the volume will contain the git - repository in the subdirectory with the given - name. + description: |- + directory is the target directory name. + Must not contain or start with '..'. If '.' is supplied, the volume directory will be the + git repository. Otherwise, if specified, the volume will contain the git repository in + the subdirectory with the given name. type: string repository: - description: Repository URL + description: repository is the URL type: string revision: - description: Commit hash for the specified revision. + description: revision is the commit hash for the + specified revision. type: string required: - repository type: object glusterfs: - description: 'Glusterfs represents a Glusterfs mount - on the host that shares a pod''s lifetime. More - info: https://examples.k8s.io/volumes/glusterfs/README.md' + description: |- + glusterfs represents a Glusterfs mount on the host that shares a pod's lifetime. + More info: https://examples.k8s.io/volumes/glusterfs/README.md properties: endpoints: - description: 'EndpointsName is the endpoint name - that details Glusterfs topology. More info: - https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + description: |- + endpoints is the endpoint name that details Glusterfs topology. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod type: string path: - description: 'Path is the Glusterfs volume path. - More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + description: |- + path is the Glusterfs volume path. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod type: string readOnly: - description: 'ReadOnly here will force the Glusterfs - volume to be mounted with read-only permissions. - Defaults to false. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + description: |- + readOnly here will force the Glusterfs volume to be mounted with read-only permissions. + Defaults to false. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod type: boolean required: - endpoints - path type: object hostPath: - description: 'HostPath represents a pre-existing file - or directory on the host machine that is directly - exposed to the container. This is generally used - for system agents or other privileged things that - are allowed to see the host machine. Most containers - will NOT need this. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath - --- TODO(jonesdl) We need to restrict who can use - host directory mounts and who can/can not mount - host directories as read/write.' + description: |- + hostPath represents a pre-existing file or directory on the host + machine that is directly exposed to the container. This is generally + used for system agents or other privileged things that are allowed + to see the host machine. Most containers will NOT need this. + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + --- + TODO(jonesdl) We need to restrict who can use host directory mounts and who can/can not + mount host directories as read/write. properties: path: - description: 'Path of the directory on the host. - If the path is a symlink, it will follow the - link to the real path. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath' + description: |- + path of the directory on the host. + If the path is a symlink, it will follow the link to the real path. + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath type: string type: - description: 'Type for HostPath Volume Defaults - to "" More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath' + description: |- + type for HostPath Volume + Defaults to "" + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath type: string required: - path type: object iscsi: - description: 'ISCSI represents an ISCSI Disk resource - that is attached to a kubelet''s host machine and - then exposed to the pod. More info: https://examples.k8s.io/volumes/iscsi/README.md' + description: |- + iscsi represents an ISCSI Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + More info: https://examples.k8s.io/volumes/iscsi/README.md properties: chapAuthDiscovery: - description: whether support iSCSI Discovery CHAP - authentication + description: chapAuthDiscovery defines whether + support iSCSI Discovery CHAP authentication type: boolean chapAuthSession: - description: whether support iSCSI Session CHAP - authentication + description: chapAuthSession defines whether support + iSCSI Session CHAP authentication type: boolean fsType: - description: 'Filesystem type of the volume that - you want to mount. Tip: Ensure that the filesystem - type is supported by the host operating system. - Examples: "ext4", "xfs", "ntfs". Implicitly - inferred to be "ext4" if unspecified. More info: - https://kubernetes.io/docs/concepts/storage/volumes#iscsi - TODO: how do we prevent errors in the filesystem - from compromising the machine' + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi + TODO: how do we prevent errors in the filesystem from compromising the machine type: string initiatorName: - description: Custom iSCSI Initiator Name. If initiatorName - is specified with iscsiInterface simultaneously, - new iSCSI interface : will be created for the connection. + description: |- + initiatorName is the custom iSCSI Initiator Name. + If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface + : will be created for the connection. type: string iqn: - description: Target iSCSI Qualified Name. + description: iqn is the target iSCSI Qualified + Name. type: string iscsiInterface: - description: iSCSI Interface Name that uses an - iSCSI transport. Defaults to 'default' (tcp). + description: |- + iscsiInterface is the interface Name that uses an iSCSI transport. + Defaults to 'default' (tcp). type: string lun: - description: iSCSI Target Lun number. + description: lun represents iSCSI Target Lun number. format: int32 type: integer portals: - description: iSCSI Target Portal List. The portal - is either an IP or ip_addr:port if the port - is other than default (typically TCP ports 860 - and 3260). + description: |- + portals is the iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port + is other than default (typically TCP ports 860 and 3260). items: type: string type: array readOnly: - description: ReadOnly here will force the ReadOnly - setting in VolumeMounts. Defaults to false. + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. type: boolean secretRef: - description: CHAP Secret for iSCSI target and - initiator authentication + description: secretRef is the CHAP Secret for + iSCSI target and initiator authentication properties: name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? type: string type: object + x-kubernetes-map-type: atomic targetPortal: - description: iSCSI Target Portal. The Portal is - either an IP or ip_addr:port if the port is - other than default (typically TCP ports 860 - and 3260). + description: |- + targetPortal is iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port + is other than default (typically TCP ports 860 and 3260). type: string required: - iqn @@ -1354,159 +1405,152 @@ spec: - targetPortal type: object name: - description: 'Volume''s name. Must be a DNS_LABEL - and unique within the pod. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + description: |- + name of the volume. + Must be a DNS_LABEL and unique within the pod. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names type: string nfs: - description: 'NFS represents an NFS mount on the host - that shares a pod''s lifetime More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + description: |- + nfs represents an NFS mount on the host that shares a pod's lifetime + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs properties: path: - description: 'Path that is exported by the NFS - server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + description: |- + path that is exported by the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs type: string readOnly: - description: 'ReadOnly here will force the NFS - export to be mounted with read-only permissions. - Defaults to false. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + description: |- + readOnly here will force the NFS export to be mounted with read-only permissions. + Defaults to false. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs type: boolean server: - description: 'Server is the hostname or IP address - of the NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + description: |- + server is the hostname or IP address of the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs type: string required: - path - server type: object persistentVolumeClaim: - description: 'PersistentVolumeClaimVolumeSource represents - a reference to a PersistentVolumeClaim in the same - namespace. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' + description: |- + persistentVolumeClaimVolumeSource represents a reference to a + PersistentVolumeClaim in the same namespace. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims properties: claimName: - description: 'ClaimName is the name of a PersistentVolumeClaim - in the same namespace as the pod using this - volume. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' + description: |- + claimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims type: string readOnly: - description: Will force the ReadOnly setting in - VolumeMounts. Default false. + description: |- + readOnly Will force the ReadOnly setting in VolumeMounts. + Default false. type: boolean required: - claimName type: object photonPersistentDisk: - description: PhotonPersistentDisk represents a PhotonController + description: photonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine properties: fsType: - description: Filesystem type to mount. Must be - a filesystem type supported by the host operating - system. Ex. "ext4", "xfs", "ntfs". Implicitly - inferred to be "ext4" if unspecified. + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. type: string pdID: - description: ID that identifies Photon Controller - persistent disk + description: pdID is the ID that identifies Photon + Controller persistent disk type: string required: - pdID type: object portworxVolume: - description: PortworxVolume represents a portworx + description: portworxVolume represents a portworx volume attached and mounted on kubelets host machine properties: fsType: - description: FSType represents the filesystem - type to mount Must be a filesystem type supported - by the host operating system. Ex. "ext4", "xfs". - Implicitly inferred to be "ext4" if unspecified. + description: |- + fSType represents the filesystem type to mount + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs". Implicitly inferred to be "ext4" if unspecified. type: string readOnly: - description: Defaults to false (read/write). ReadOnly - here will force the ReadOnly setting in VolumeMounts. + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. type: boolean volumeID: - description: VolumeID uniquely identifies a Portworx + description: volumeID uniquely identifies a Portworx volume type: string required: - volumeID type: object projected: - description: Items for all in one resources secrets, - configmaps, and downward API + description: projected items for all in one resources + secrets, configmaps, and downward API properties: defaultMode: - description: Mode bits used to set permissions - on created files by default. Must be an octal - value between 0000 and 0777 or a decimal value - between 0 and 511. YAML accepts both octal and - decimal values, JSON requires decimal values - for mode bits. Directories within the path are - not affected by this setting. This might be - in conflict with other options that affect the - file mode, like fsGroup, and the result can - be other mode bits set. + description: |- + defaultMode are the mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. format: int32 type: integer sources: - description: list of volume projections + description: sources is the list of volume projections items: description: Projection that may be projected along with other supported volume types properties: configMap: - description: information about the configMap - data to project + description: configMap information about + the configMap data to project properties: items: - description: If unspecified, each key-value - pair in the Data field of the referenced - ConfigMap will be projected into the - volume as a file whose name is the - key and content is the value. If specified, - the listed keys will be projected - into the specified paths, and unlisted - keys will not be present. If a key - is specified which is not present - in the ConfigMap, the volume setup - will error unless it is marked optional. - Paths must be relative and may not - contain the '..' path or start with - '..'. + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. items: description: Maps a string key to a path within a volume. properties: key: - description: The key to project. + description: key is the key to + project. type: string mode: - description: 'Optional: mode bits - used to set permissions on this - file. Must be an octal value - between 0000 and 0777 or a decimal - value between 0 and 511. YAML - accepts both octal and decimal - values, JSON requires decimal - values for mode bits. If not - specified, the volume defaultMode - will be used. This might be - in conflict with other options - that affect the file mode, like - fsGroup, and the result can - be other mode bits set.' + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. format: int32 type: integer path: - description: The relative path - of the file to map the key to. + description: |- + path is the relative path of the file to map the key to. May not be an absolute path. - May not contain the path element - '..'. May not start with the - string '..'. + May not contain the path element '..'. + May not start with the string '..'. type: string required: - key @@ -1514,19 +1558,21 @@ spec: type: object type: array name: - description: 'Name of the referent. + description: |- + Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' + TODO: Add other useful fields. apiVersion, kind, uid? type: string optional: - description: Specify whether the ConfigMap - or its keys must be defined + description: optional specify whether + the ConfigMap or its keys must be + defined type: boolean type: object + x-kubernetes-map-type: atomic downwardAPI: - description: information about the downwardAPI - data to project + description: downwardAPI information about + the downwardAPI data to project properties: items: description: Items is a list of DownwardAPIVolume @@ -1556,21 +1602,15 @@ spec: required: - fieldPath type: object + x-kubernetes-map-type: atomic mode: - description: 'Optional: mode bits - used to set permissions on this - file, must be an octal value - between 0000 and 0777 or a decimal - value between 0 and 511. YAML - accepts both octal and decimal - values, JSON requires decimal - values for mode bits. If not - specified, the volume defaultMode - will be used. This might be - in conflict with other options - that affect the file mode, like - fsGroup, and the result can - be other mode bits set.' + description: |- + Optional: mode bits used to set permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. format: int32 type: integer path: @@ -1583,12 +1623,9 @@ spec: not start with ''..''' type: string resourceFieldRef: - description: 'Selects a resource - of the container: only resources - limits and requests (limits.cpu, - limits.memory, requests.cpu - and requests.memory) are currently - supported.' + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. properties: containerName: description: 'Container name: @@ -1611,61 +1648,49 @@ spec: required: - resource type: object + x-kubernetes-map-type: atomic required: - path type: object type: array type: object secret: - description: information about the secret - data to project + description: secret information about the + secret data to project properties: items: - description: If unspecified, each key-value - pair in the Data field of the referenced - Secret will be projected into the - volume as a file whose name is the - key and content is the value. If specified, - the listed keys will be projected - into the specified paths, and unlisted - keys will not be present. If a key - is specified which is not present - in the Secret, the volume setup will - error unless it is marked optional. - Paths must be relative and may not - contain the '..' path or start with - '..'. + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. items: description: Maps a string key to a path within a volume. properties: key: - description: The key to project. + description: key is the key to + project. type: string mode: - description: 'Optional: mode bits - used to set permissions on this - file. Must be an octal value - between 0000 and 0777 or a decimal - value between 0 and 511. YAML - accepts both octal and decimal - values, JSON requires decimal - values for mode bits. If not - specified, the volume defaultMode - will be used. This might be - in conflict with other options - that affect the file mode, like - fsGroup, and the result can - be other mode bits set.' + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. format: int32 type: integer path: - description: The relative path - of the file to map the key to. + description: |- + path is the relative path of the file to map the key to. May not be an absolute path. - May not contain the path element - '..'. May not start with the - string '..'. + May not contain the path element '..'. + May not start with the string '..'. type: string required: - key @@ -1673,47 +1698,44 @@ spec: type: object type: array name: - description: 'Name of the referent. + description: |- + Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' + TODO: Add other useful fields. apiVersion, kind, uid? type: string optional: - description: Specify whether the Secret - or its key must be defined + description: optional field specify + whether the Secret or its key must + be defined type: boolean type: object + x-kubernetes-map-type: atomic serviceAccountToken: - description: information about the serviceAccountToken - data to project + description: serviceAccountToken is information + about the serviceAccountToken data to + project properties: audience: - description: Audience is the intended - audience of the token. A recipient - of a token must identify itself with - an identifier specified in the audience - of the token, and otherwise should - reject the token. The audience defaults - to the identifier of the apiserver. + description: |- + audience is the intended audience of the token. A recipient of a token + must identify itself with an identifier specified in the audience of the + token, and otherwise should reject the token. The audience defaults to the + identifier of the apiserver. type: string expirationSeconds: - description: ExpirationSeconds is the - requested duration of validity of - the service account token. As the - token approaches expiration, the kubelet - volume plugin will proactively rotate - the service account token. The kubelet - will start trying to rotate the token - if the token is older than 80 percent - of its time to live or if the token - is older than 24 hours.Defaults to - 1 hour and must be at least 10 minutes. + description: |- + expirationSeconds is the requested duration of validity of the service + account token. As the token approaches expiration, the kubelet volume + plugin will proactively rotate the service account token. The kubelet will + start trying to rotate the token if the token is older than 80 percent of + its time to live or if the token is older than 24 hours.Defaults to 1 hour + and must be at least 10 minutes. format: int64 type: integer path: - description: Path is the path relative - to the mount point of the file to - project the token into. + description: |- + path is the path relative to the mount point of the file to project the + token into. type: string required: - path @@ -1722,36 +1744,37 @@ spec: type: array type: object quobyte: - description: Quobyte represents a Quobyte mount on + description: quobyte represents a Quobyte mount on the host that shares a pod's lifetime properties: group: - description: Group to map volume access to Default - is no group + description: |- + group to map volume access to + Default is no group type: string readOnly: - description: ReadOnly here will force the Quobyte - volume to be mounted with read-only permissions. + description: |- + readOnly here will force the Quobyte volume to be mounted with read-only permissions. Defaults to false. type: boolean registry: - description: Registry represents a single or multiple - Quobyte Registry services specified as a string - as host:port pair (multiple entries are separated - with commas) which acts as the central registry - for volumes + description: |- + registry represents a single or multiple Quobyte Registry services + specified as a string as host:port pair (multiple entries are separated with commas) + which acts as the central registry for volumes type: string tenant: - description: Tenant owning the given Quobyte volume - in the Backend Used with dynamically provisioned - Quobyte volumes, value is set by the plugin + description: |- + tenant owning the given Quobyte volume in the Backend + Used with dynamically provisioned Quobyte volumes, value is set by the plugin type: string user: - description: User to map volume access to Defaults - to serivceaccount user + description: |- + user to map volume access to + Defaults to serivceaccount user type: string volume: - description: Volume is a string that references + description: volume is a string that references an already created Quobyte volume by name. type: string required: @@ -1759,120 +1782,132 @@ spec: - volume type: object rbd: - description: 'RBD represents a Rados Block Device - mount on the host that shares a pod''s lifetime. - More info: https://examples.k8s.io/volumes/rbd/README.md' + description: |- + rbd represents a Rados Block Device mount on the host that shares a pod's lifetime. + More info: https://examples.k8s.io/volumes/rbd/README.md properties: fsType: - description: 'Filesystem type of the volume that - you want to mount. Tip: Ensure that the filesystem - type is supported by the host operating system. - Examples: "ext4", "xfs", "ntfs". Implicitly - inferred to be "ext4" if unspecified. More info: - https://kubernetes.io/docs/concepts/storage/volumes#rbd - TODO: how do we prevent errors in the filesystem - from compromising the machine' + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd + TODO: how do we prevent errors in the filesystem from compromising the machine type: string image: - description: 'The rados image name. More info: - https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + description: |- + image is the rados image name. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it type: string keyring: - description: 'Keyring is the path to key ring - for RBDUser. Default is /etc/ceph/keyring. More - info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + description: |- + keyring is the path to key ring for RBDUser. + Default is /etc/ceph/keyring. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it type: string monitors: - description: 'A collection of Ceph monitors. More - info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + description: |- + monitors is a collection of Ceph monitors. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it items: type: string type: array pool: - description: 'The rados pool name. Default is - rbd. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + description: |- + pool is the rados pool name. + Default is rbd. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it type: string readOnly: - description: 'ReadOnly here will force the ReadOnly - setting in VolumeMounts. Defaults to false. - More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it type: boolean secretRef: - description: 'SecretRef is name of the authentication - secret for RBDUser. If provided overrides keyring. - Default is nil. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + description: |- + secretRef is name of the authentication secret for RBDUser. If provided + overrides keyring. + Default is nil. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it properties: name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? type: string type: object + x-kubernetes-map-type: atomic user: - description: 'The rados user name. Default is - admin. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + description: |- + user is the rados user name. + Default is admin. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it type: string required: - image - monitors type: object scaleIO: - description: ScaleIO represents a ScaleIO persistent + description: scaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes. properties: fsType: - description: Filesystem type to mount. Must be - a filesystem type supported by the host operating - system. Ex. "ext4", "xfs", "ntfs". Default is - "xfs". + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". + Default is "xfs". type: string gateway: - description: The host address of the ScaleIO API - Gateway. + description: gateway is the host address of the + ScaleIO API Gateway. type: string protectionDomain: - description: The name of the ScaleIO Protection - Domain for the configured storage. + description: protectionDomain is the name of the + ScaleIO Protection Domain for the configured + storage. type: string readOnly: - description: Defaults to false (read/write). ReadOnly - here will force the ReadOnly setting in VolumeMounts. + description: |- + readOnly Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. type: boolean secretRef: - description: SecretRef references to the secret - for ScaleIO user and other sensitive information. - If this is not provided, Login operation will - fail. + description: |- + secretRef references to the secret for ScaleIO user and other + sensitive information. If this is not provided, Login operation will fail. properties: name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? type: string type: object + x-kubernetes-map-type: atomic sslEnabled: - description: Flag to enable/disable SSL communication - with Gateway, default false + description: sslEnabled Flag enable/disable SSL + communication with Gateway, default false type: boolean storageMode: - description: Indicates whether the storage for - a volume should be ThickProvisioned or ThinProvisioned. + description: |- + storageMode indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned. Default is ThinProvisioned. type: string storagePool: - description: The ScaleIO Storage Pool associated - with the protection domain. + description: storagePool is the ScaleIO Storage + Pool associated with the protection domain. type: string system: - description: The name of the storage system as - configured in ScaleIO. + description: system is the name of the storage + system as configured in ScaleIO. type: string volumeName: - description: The name of a volume already created - in the ScaleIO system that is associated with - this volume source. + description: |- + volumeName is the name of a volume already created in the ScaleIO system + that is associated with this volume source. type: string required: - gateway @@ -1880,61 +1915,53 @@ spec: - system type: object secret: - description: 'Secret represents a secret that should - populate this volume. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret' + description: |- + secret represents a secret that should populate this volume. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret properties: defaultMode: - description: 'Optional: mode bits used to set - permissions on created files by default. Must - be an octal value between 0000 and 0777 or a - decimal value between 0 and 511. YAML accepts - both octal and decimal values, JSON requires - decimal values for mode bits. Defaults to 0644. - Directories within the path are not affected - by this setting. This might be in conflict with - other options that affect the file mode, like - fsGroup, and the result can be other mode bits - set.' + description: |- + defaultMode is Optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values + for mode bits. Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. format: int32 type: integer items: - description: If unspecified, each key-value pair - in the Data field of the referenced Secret will - be projected into the volume as a file whose - name is the key and content is the value. If - specified, the listed keys will be projected - into the specified paths, and unlisted keys - will not be present. If a key is specified which - is not present in the Secret, the volume setup - will error unless it is marked optional. Paths - must be relative and may not contain the '..' - path or start with '..'. + description: |- + items If unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. items: description: Maps a string key to a path within a volume. properties: key: - description: The key to project. + description: key is the key to project. type: string mode: - description: 'Optional: mode bits used to - set permissions on this file. Must be - an octal value between 0000 and 0777 or - a decimal value between 0 and 511. YAML - accepts both octal and decimal values, - JSON requires decimal values for mode - bits. If not specified, the volume defaultMode - will be used. This might be in conflict - with other options that affect the file - mode, like fsGroup, and the result can - be other mode bits set.' + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. format: int32 type: integer path: - description: The relative path of the file - to map the key to. May not be an absolute - path. May not contain the path element - '..'. May not start with the string '..'. + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. type: string required: - key @@ -1942,79 +1969,80 @@ spec: type: object type: array optional: - description: Specify whether the Secret or its - keys must be defined + description: optional field specify whether the + Secret or its keys must be defined type: boolean secretName: - description: 'Name of the secret in the pod''s - namespace to use. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret' + description: |- + secretName is the name of the secret in the pod's namespace to use. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret type: string type: object storageos: - description: StorageOS represents a StorageOS volume + description: storageOS represents a StorageOS volume attached and mounted on Kubernetes nodes. properties: fsType: - description: Filesystem type to mount. Must be - a filesystem type supported by the host operating - system. Ex. "ext4", "xfs", "ntfs". Implicitly - inferred to be "ext4" if unspecified. + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. type: string readOnly: - description: Defaults to false (read/write). ReadOnly - here will force the ReadOnly setting in VolumeMounts. + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. type: boolean secretRef: - description: SecretRef specifies the secret to - use for obtaining the StorageOS API credentials. If - not specified, default values will be attempted. + description: |- + secretRef specifies the secret to use for obtaining the StorageOS API + credentials. If not specified, default values will be attempted. properties: name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? type: string type: object + x-kubernetes-map-type: atomic volumeName: - description: VolumeName is the human-readable - name of the StorageOS volume. Volume names - are only unique within a namespace. + description: |- + volumeName is the human-readable name of the StorageOS volume. Volume + names are only unique within a namespace. type: string volumeNamespace: - description: VolumeNamespace specifies the scope - of the volume within StorageOS. If no namespace - is specified then the Pod's namespace will be - used. This allows the Kubernetes name scoping - to be mirrored within StorageOS for tighter - integration. Set VolumeName to any name to override - the default behaviour. Set to "default" if you - are not using namespaces within StorageOS. Namespaces - that do not pre-exist within StorageOS will - be created. + description: |- + volumeNamespace specifies the scope of the volume within StorageOS. If no + namespace is specified then the Pod's namespace will be used. This allows the + Kubernetes name scoping to be mirrored within StorageOS for tighter integration. + Set VolumeName to any name to override the default behaviour. + Set to "default" if you are not using namespaces within StorageOS. + Namespaces that do not pre-exist within StorageOS will be created. type: string type: object vsphereVolume: - description: VsphereVolume represents a vSphere volume + description: vsphereVolume represents a vSphere volume attached and mounted on kubelets host machine properties: fsType: - description: Filesystem type to mount. Must be - a filesystem type supported by the host operating - system. Ex. "ext4", "xfs", "ntfs". Implicitly - inferred to be "ext4" if unspecified. + description: |- + fsType is filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. type: string storagePolicyID: - description: Storage Policy Based Management (SPBM) - profile ID associated with the StoragePolicyName. + description: storagePolicyID is the storage Policy + Based Management (SPBM) profile ID associated + with the StoragePolicyName. type: string storagePolicyName: - description: Storage Policy Based Management (SPBM) - profile name. + description: storagePolicyName is the storage + Policy Based Management (SPBM) profile name. type: string volumePath: - description: Path that identifies vSphere volume - vmdk + description: volumePath is the path that identifies + vSphere volume vmdk type: string required: - volumePath @@ -2024,24 +2052,21 @@ spec: type: object type: array type: object - supportStatus: - description: 'SupportStatus is used to indicate the support - status for services. Valid values are: - "continuous" (default): - operator is supported to be fresh deployed via OperandRequest - from scratch - "maintained" operator is not supported to be - fresh deployed via OperandRequest, only upgrade and deletion - are allowed' - type: string targetNamespaces: description: The target namespace of the OperatorGroups. items: type: string type: array + userManaged: + description: UserManaged is a flag to indicate whether operator + is managed by user + type: boolean required: - channel - name - packageName type: object + x-kubernetes-preserve-unknown-fields: true type: array type: object x-kubernetes-preserve-unknown-fields: true @@ -2052,8 +2077,9 @@ spec: description: Conditions represents the current state of the Request Service. items: - description: Condition represents the current state of the Request - Service. A condition might not show up if it is not happening. + description: |- + Condition represents the current state of the Request Service. + A condition might not show up if it is not happening. properties: lastTransitionTime: description: Last time the condition transitioned from one status @@ -2115,14 +2141,9 @@ spec: OperandRegistry. type: string type: object + x-kubernetes-preserve-unknown-fields: true type: object served: true storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/config/crd/bases/operator.ibm.com_operandrequests.yaml b/config/crd/bases/operator.ibm.com_operandrequests.yaml index df4f8258..047888fe 100644 --- a/config/crd/bases/operator.ibm.com_operandrequests.yaml +++ b/config/crd/bases/operator.ibm.com_operandrequests.yaml @@ -1,11 +1,9 @@ - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.1 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.14.0 name: operandrequests.operator.ibm.com spec: group: operator.ibm.com @@ -32,17 +30,24 @@ spec: name: v1alpha1 schema: openAPIV3Schema: - description: OperandRequest is the Schema for the operandrequests API. + description: OperandRequest is the Schema for the operandrequests API. Documentation + For additional details regarding install parameters check https://ibm.biz/icpfs39install. + License By installing this product you accept the license terms https://ibm.biz/icpfs39license properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -72,40 +77,79 @@ spec: type: string bindings: additionalProperties: - description: SecretConfigmap is a pair of Secret and/or - Configmap. + description: |- + Bindable is a Kubernetes resources to be shared from one namespace to another. + List of supported resources are Secrets, Configmaps, Services, and Routes. + Secrets and Configmaps will be copied such that a new Secret/Configmap with + exactly the same data will be created in the target namespace. + Services and Routes data will be copied into a configmap in the target + namespace. properties: configmap: description: The configmap identifies an existing configmap object. if it exists, the ODLM will share to the namespace of the OperandRequest. type: string + route: + description: |- + Route data will be shared by copying it into a configmap which is then + created in the target namespace + properties: + data: + additionalProperties: + type: string + description: |- + Data is a key-value pair where the value is a YAML path to a value in the + OpenShift Route, e.g. .spec.host or .spec.tls.termination + type: object + name: + description: Name is the name of the OpenShift + Route resource + type: string + type: object secret: description: The secret identifies an existing secret. if it exists, the ODLM will share to the namespace of the OperandRequest. type: string + service: + description: |- + Service data will be shared by copying it into a configmap which is then + created in the target namespace + properties: + data: + additionalProperties: + type: string + description: |- + Data is a key-value pair where the value is a YAML path to a value in the + Kubernetes Service, e.g. .spec.ports[0]port + type: object + name: + description: Name is the name of the Kubernetes + Service resource + type: string + type: object type: object description: The bindings section is used to specify names of secret and/or configmap. type: object instanceName: - description: InstanceName is used when users want to deploy - multiple custom resources. It is the name of the custom - resource. + description: |- + InstanceName is used when users want to deploy multiple custom resources. + It is the name of the custom resource. type: string kind: - description: Kind is used when users want to deploy multiple - custom resources. Kind identifies the kind of the custom - resource. + description: |- + Kind is used when users want to deploy multiple custom resources. + Kind identifies the kind of the custom resource. type: string name: description: Name of the operand to be deployed. type: string spec: - description: Spec is used when users want to deploy multiple - custom resources. It is the configuration map of custom - resource. + description: |- + Spec is used when users want to deploy multiple custom resources. + It is the configuration map of custom resource. nullable: true type: object x-kubernetes-preserve-unknown-fields: true @@ -118,9 +162,9 @@ spec: reside. type: string registryNamespace: - description: Specifies the namespace in which the OperandRegistry - reside. The default is the current namespace in which the - request is defined. + description: |- + Specifies the namespace in which the OperandRegistry reside. + The default is the current namespace in which the request is defined. type: string required: - operands @@ -138,8 +182,9 @@ spec: description: Conditions represents the current state of the Request Service. items: - description: Condition represents the current state of the Request - Service. A condition might not show up if it is not happening. + description: |- + Condition represents the current state of the Request Service. + A condition might not show up if it is not happening. properties: lastTransitionTime: description: Last time the condition transitioned from one status @@ -215,15 +260,57 @@ spec: phase: description: Phase is the cluster running phase. type: string + services: + description: Services reflect the status of operands beyond whether + they have been created + items: + properties: + namespace: + type: string + operatorName: + type: string + resources: + description: LastUpdateTime string `json:"lastTransitionTime,omitempty"` + items: + properties: + apiVersion: + type: string + kind: + type: string + managedResources: + description: Message string `json:"message,omitempty"` + items: + properties: + apiVersion: + type: string + kind: + type: string + namespace: + type: string + objectName: + type: string + status: + description: Type string `json:"type,omitempty"` + type: string + type: object + type: array + namespace: + type: string + objectName: + type: string + status: + type: string + type: object + type: array + status: + description: Type string `json:"type,omitempty"` + type: string + type: object + type: array type: object + x-kubernetes-preserve-unknown-fields: true type: object served: true storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/config/crd/bases/operator.ibm.com_operatorconfigs.yaml b/config/crd/bases/operator.ibm.com_operatorconfigs.yaml new file mode 100644 index 00000000..168b1483 --- /dev/null +++ b/config/crd/bases/operator.ibm.com_operatorconfigs.yaml @@ -0,0 +1,1007 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + name: operatorconfigs.operator.ibm.com +spec: + group: operator.ibm.com + names: + kind: OperatorConfig + listKind: OperatorConfigList + plural: operatorconfigs + singular: operatorconfig + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - description: Current Phase + jsonPath: .status.phase + name: Phase + type: string + - jsonPath: .metadata.creationTimestamp + name: Created At + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: OperatorConfig is the Schema for the operatorconfigs API. Documentation + For additional details regarding install parameters check https://ibm.biz/icpfs39install. + License By installing this product you accept the license terms https://ibm.biz/icpfs39license + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: OperatorConfigSpec defines the desired state of OperatorConfig + properties: + foo: + description: Foo is an example field of OperatorConfig. Edit operatorconfig_types.go + to remove/update + type: string + services: + description: Services is a list of services to be configured, specifically + their operators + items: + description: ServiceOperatorConfig defines the configuration of + the service. + properties: + affinity: + description: If specified, the pod's scheduling constraints + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for + the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the + selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the + selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + x-kubernetes-map-type: atomic + weight: + description: Weight associated with matching the + corresponding nodeSelectorTerm, in the range + 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. + The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the + selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the + selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + x-kubernetes-map-type: atomic + type: array + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. + co-locate this pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of + label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of + label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules + (e.g. avoid putting this pod in the same node, zone, etc. + as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of + label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of + label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + name: + description: Name is the operator name as requested in the OperandRequest. + type: string + replicas: + description: |- + Number of desired pods. This is a pointer to distinguish between explicit + zero and not specified. Defaults to 1. + format: int32 + type: integer + topologySpreadConstraints: + description: |- + TopologySpreadConstraints describes how a group of pods ought to spread across topology + domains. Scheduler will schedule pods in a way which abides by the constraints. + All topologySpreadConstraints are ANDed. + items: + description: TopologySpreadConstraint specifies how to spread + matching pods among the given topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + maxSkew: + description: |- + MaxSkew describes the degree to which pods may be unevenly distributed. + When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference + between the number of matching pods in the target topology and the global minimum. + The global minimum is the minimum number of matching pods in an eligible domain + or zero if the number of eligible domains is less than MinDomains. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 2/2/1: + In this case, the global minimum is 1. + | zone1 | zone2 | zone3 | + | P P | P P | P | + - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; + scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) + violate MaxSkew(1). + - if MaxSkew is 2, incoming pod can be scheduled onto any zone. + When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence + to topologies that satisfy it. + It's a required field. Default value is 1 and 0 is not allowed. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + + + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + + + This is an alpha field and requires enabling MinDomainsInPodTopologySpread feature gate. + format: int32 + type: integer + topologyKey: + description: |- + TopologyKey is the key of node labels. Nodes that have a label with this key + and identical values are considered to be in the same topology. + We consider each as a "bucket", and try to put balanced number + of pods into each bucket. + We define a domain as a particular instance of a topology. + Also, we define an eligible domain as a domain whose nodes match the node selector. + e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. + And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. + It's a required field. + type: string + whenUnsatisfiable: + description: |- + WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy + the spread constraint. + - DoNotSchedule (default) tells the scheduler not to schedule it. + - ScheduleAnyway tells the scheduler to schedule the pod in any location, + but giving higher precedence to topologies that would help reduce the + skew. + A constraint is considered "Unsatisfiable" for an incoming pod + if and only if every possible node assignment for that pod would violate + "MaxSkew" on some topology. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 3/1/1: + | zone1 | zone2 | zone3 | + | P P P | P | P | + If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled + to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies + MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler + won't make it *more* imbalanced. + It's a required field. + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + x-kubernetes-list-map-keys: + - topologyKey + - whenUnsatisfiable + x-kubernetes-list-type: map + required: + - name + type: object + type: array + x-kubernetes-preserve-unknown-fields: true + type: object + x-kubernetes-preserve-unknown-fields: true + status: + description: OperatorConfigStatus defines the observed state of OperatorConfig + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index be422ac7..b50b14f2 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -6,6 +6,7 @@ resources: - bases/operator.ibm.com_operandconfigs.yaml - bases/operator.ibm.com_operandbindinfos.yaml - bases/operator.ibm.com_operandregistries.yaml +- bases/operator.ibm.com_operatorconfigs.yaml # +kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: @@ -15,6 +16,7 @@ patchesStrategicMerge: #- patches/webhook_in_operandconfigs.yaml #- patches/webhook_in_operandbindinfoes.yaml #- patches/webhook_in_operandregistries.yaml +#- patches/webhook_in_operatorconfigs.yaml # +kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix. @@ -23,6 +25,7 @@ patchesStrategicMerge: #- patches/cainjection_in_operandconfigs.yaml #- patches/cainjection_in_operandbindinfoes.yaml #- patches/cainjection_in_operandregistries.yaml +#- patches/cainjection_in_operatorconfigs.yaml # +kubebuilder:scaffold:crdkustomizecainjectionpatch # patches here are for adding labels for each CRD @@ -30,6 +33,8 @@ patchesStrategicMerge: - patches/label_in_operandconfigs.yaml - patches/label_in_operandbindinfos.yaml - patches/label_in_operandregistries.yaml +- patches/label_in_operatorconfigs.yaml +#- patches/cainjection_in_operatorconfigs.yaml # +kubebuilder:scaffold:crdkustomizecainjectionpatch # the following config is for teaching kustomize how to do kustomization for CRDs. diff --git a/config/crd/patches/label_in_operatorconfigs.yaml b/config/crd/patches/label_in_operatorconfigs.yaml new file mode 100644 index 00000000..49773cb2 --- /dev/null +++ b/config/crd/patches/label_in_operatorconfigs.yaml @@ -0,0 +1,9 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + labels: + app.kubernetes.io/instance: "operand-deployment-lifecycle-manager" + app.kubernetes.io/managed-by: "operand-deployment-lifecycle-manager" + app.kubernetes.io/name: "operand-deployment-lifecycle-manager" + name: operatorconfigs.operator.ibm.com diff --git a/config/e2e/crd/bases/operator.ibm.com_operandbindinfos.yaml b/config/e2e/crd/bases/operator.ibm.com_operandbindinfos.yaml index d2a048e7..0b560cde 100644 --- a/config/e2e/crd/bases/operator.ibm.com_operandbindinfos.yaml +++ b/config/e2e/crd/bases/operator.ibm.com_operandbindinfos.yaml @@ -51,22 +51,57 @@ spec: metadata: type: object spec: - x-kubernetes-preserve-unknown-fields: true description: OperandBindInfoSpec defines the desired state of OperandBindInfo. properties: bindings: additionalProperties: - description: SecretConfigmap is a pair of Secret and/or Configmap. + description: Bindable is a Kubernetes resources to be shared from + one namespace to another. List of supported resources are Secrets, + Configmaps, Services, and Routes. Secrets and Configmaps will + be copied such that a new Secret/Configmap with exactly the same + data will be created in the target namespace. Services and Routes + data will be copied into a configmap in the target namespace. properties: configmap: description: The configmap identifies an existing configmap object. if it exists, the ODLM will share to the namespace of the OperandRequest. type: string + route: + description: Route data will be shared by copying it into a + configmap which is then created in the target namespace + properties: + data: + additionalProperties: + type: string + description: Data is a key-value pair where the value is + a YAML path to a value in the OpenShift Route, e.g. .spec.host + or .spec.tls.termination + type: object + name: + description: Name is the name of the OpenShift Route resource + type: string + type: object secret: description: The secret identifies an existing secret. if it exists, the ODLM will share to the namespace of the OperandRequest. type: string + service: + description: Service data will be shared by copying it into + a configmap which is then created in the target namespace + properties: + data: + additionalProperties: + type: string + description: Data is a key-value pair where the value is + a YAML path to a value in the Kubernetes Service, e.g. + .spec.ports[0]port + type: object + name: + description: Name is the name of the Kubernetes Service + resource + type: string + type: object type: object description: The bindings section is used to specify information about the access/configuration data that is to be shared. @@ -90,6 +125,7 @@ spec: - operand - registry type: object + x-kubernetes-preserve-unknown-fields: true status: description: OperandBindInfoStatus defines the observed state of OperandBindInfo. properties: @@ -102,6 +138,7 @@ spec: type: string type: array type: object + x-kubernetes-preserve-unknown-fields: true type: object served: true storage: true diff --git a/config/e2e/crd/bases/operator.ibm.com_operandrequests.yaml b/config/e2e/crd/bases/operator.ibm.com_operandrequests.yaml index 4089c705..770a90e7 100644 --- a/config/e2e/crd/bases/operator.ibm.com_operandrequests.yaml +++ b/config/e2e/crd/bases/operator.ibm.com_operandrequests.yaml @@ -51,7 +51,6 @@ spec: metadata: type: object spec: - x-kubernetes-preserve-unknown-fields: true description: The OperandRequestSpec identifies one or more specific operands (from a specific Registry) that should actually be installed. properties: @@ -77,19 +76,59 @@ spec: type: string bindings: additionalProperties: - description: SecretConfigmap is a pair of Secret and/or - Configmap. + description: Bindable is a Kubernetes resources to be + shared from one namespace to another. List of supported + resources are Secrets, Configmaps, Services, and Routes. + Secrets and Configmaps will be copied such that a + new Secret/Configmap with exactly the same data will + be created in the target namespace. Services and Routes + data will be copied into a configmap in the target + namespace. properties: configmap: description: The configmap identifies an existing configmap object. if it exists, the ODLM will share to the namespace of the OperandRequest. type: string + route: + description: Route data will be shared by copying + it into a configmap which is then created in the + target namespace + properties: + data: + additionalProperties: + type: string + description: Data is a key-value pair where + the value is a YAML path to a value in the + OpenShift Route, e.g. .spec.host or .spec.tls.termination + type: object + name: + description: Name is the name of the OpenShift + Route resource + type: string + type: object secret: description: The secret identifies an existing secret. if it exists, the ODLM will share to the namespace of the OperandRequest. type: string + service: + description: Service data will be shared by copying + it into a configmap which is then created in the + target namespace + properties: + data: + additionalProperties: + type: string + description: Data is a key-value pair where + the value is a YAML path to a value in the + Kubernetes Service, e.g. .spec.ports[0]port + type: object + name: + description: Name is the name of the Kubernetes + Service resource + type: string + type: object type: object description: The bindings section is used to specify names of secret and/or configmap. @@ -135,6 +174,7 @@ spec: required: - requests type: object + x-kubernetes-preserve-unknown-fields: true status: description: OperandRequestStatus defines the observed state of OperandRequest. properties: @@ -219,7 +259,55 @@ spec: phase: description: Phase is the cluster running phase. type: string + services: + description: Services reflect the status of operands beyond whether + they have been created + items: + properties: + namespace: + type: string + operatorName: + type: string + resources: + description: LastUpdateTime string `json:"lastTransitionTime,omitempty"` + items: + properties: + apiVersion: + type: string + kind: + type: string + managedResources: + description: Message string `json:"message,omitempty"` + items: + properties: + apiVersion: + type: string + kind: + type: string + namespace: + type: string + objectName: + type: string + status: + description: Type string `json:"type,omitempty"` + type: string + type: object + type: array + namespace: + type: string + objectName: + type: string + status: + type: string + type: object + type: array + status: + description: Type string `json:"type,omitempty"` + type: string + type: object + type: array type: object + x-kubernetes-preserve-unknown-fields: true type: object served: true storage: true diff --git a/config/e2e/manager/manager.yaml b/config/e2e/manager/manager.yaml index 3cf3c6eb..25c5270d 100644 --- a/config/e2e/manager/manager.yaml +++ b/config/e2e/manager/manager.yaml @@ -57,6 +57,8 @@ spec: cpu: 200m memory: 200Mi securityContext: + seccompProfile: + type: RuntimeDefault allowPrivilegeEscalation: false capabilities: drop: diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index c2c8ee48..7f1caa7d 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -10,6 +10,7 @@ metadata: app.kubernetes.io/instance: "operand-deployment-lifecycle-manager" app.kubernetes.io/managed-by: "operand-deployment-lifecycle-manager" app.kubernetes.io/name: "operand-deployment-lifecycle-manager" + productName: IBM_Cloud_Platform_Common_Services name: operand-deployment-lifecycle-manager namespace: system spec: @@ -24,6 +25,7 @@ spec: app.kubernetes.io/instance: operand-deployment-lifecycle-manager app.kubernetes.io/managed-by: "operand-deployment-lifecycle-manager" app.kubernetes.io/name: "operand-deployment-lifecycle-manager" + productName: IBM_Cloud_Platform_Common_Services intent: projected-odlm annotations: productName: "IBM Cloud Platform Common Services" @@ -58,7 +60,7 @@ spec: fieldRef: fieldPath: metadata.annotations['olm.targetNamespaces'] image: icr.io/cpopen/odlm:latest - imagePullPolicy: Always + imagePullPolicy: IfNotPresent name: manager livenessProbe: httpGet: @@ -83,7 +85,10 @@ spec: requests: cpu: 200m memory: 200Mi + ephemeral-storage: 256Mi securityContext: + seccompProfile: + type: RuntimeDefault allowPrivilegeEscalation: false capabilities: drop: diff --git a/config/manifests/bases/operand-deployment-lifecycle-manager.clusterserviceversion.yaml b/config/manifests/bases/operand-deployment-lifecycle-manager.clusterserviceversion.yaml index 05a7b959..bdcedb80 100644 --- a/config/manifests/bases/operand-deployment-lifecycle-manager.clusterserviceversion.yaml +++ b/config/manifests/bases/operand-deployment-lifecycle-manager.clusterserviceversion.yaml @@ -10,11 +10,20 @@ metadata: createdAt: "2020-11-12T17:05:48Z" description: The Operand Deployment Lifecycle Manager provides a Kubernetes CRD-based API to manage the lifecycle of operands. - olm.skipRange: '>=1.2.0 <4.0.0' + nss.operator.ibm.com/managed-operators: ibm-odlm + olm.skipRange: '>=1.2.0 <4.4.0' + operators.openshift.io/infrastructure-features: '["disconnected"]' operators.operatorframework.io/builder: operator-sdk-v1.2.0 operators.operatorframework.io/project_layout: go.kubebuilder.io/v2 repository: https://github.com/IBM/operand-deployment-lifecycle-manager support: IBM + features.operators.openshift.io/disconnected: "true" + features.operators.openshift.io/fips-compliant: "true" + features.operators.openshift.io/proxy-aware: "false" + features.operators.openshift.io/tls-profiles: "false" + features.operators.openshift.io/token-auth-aws: "false" + features.operators.openshift.io/token-auth-azure: "false" + features.operators.openshift.io/token-auth-gcp: "false" labels: operatorframework.io/arch.amd64: supported operatorframework.io/arch.ppc64le: supported @@ -26,7 +35,9 @@ spec: apiservicedefinitions: {} customresourcedefinitions: owned: - - description: OperandBindInfo is the Schema for the operandbindinfoes API. Documentation For additional details regarding install parameters check https://ibm.biz/icpfs39install. License By installing this product you accept the license terms https://ibm.biz/icpfs39license + - description: OperandBindInfo is the Schema for the operandbindinfoes API. Documentation + For additional details regarding install parameters check https://ibm.biz/icpfs39install. + License By installing this product you accept the license terms https://ibm.biz/icpfs39license displayName: OperandBindInfo kind: OperandBindInfo name: operandbindinfos.operator.ibm.com @@ -37,7 +48,9 @@ spec: x-descriptors: - urn:alm:descriptor:io.kubernetes.phase version: v1alpha1 - - description: OperandConfig is the Schema for the operandconfigs API. Documentation For additional details regarding install parameters check https://ibm.biz/icpfs39install. License By installing this product you accept the license terms https://ibm.biz/icpfs39license + - description: OperandConfig is the Schema for the operandconfigs API. Documentation + For additional details regarding install parameters check https://ibm.biz/icpfs39install. + License By installing this product you accept the license terms https://ibm.biz/icpfs39license displayName: OperandConfig kind: OperandConfig name: operandconfigs.operator.ibm.com @@ -52,7 +65,9 @@ spec: x-descriptors: - urn:alm:descriptor:io.kubernetes.phase version: v1alpha1 - - description: OperandRegistry is the Schema for the operandregistries API. Documentation For additional details regarding install parameters check https://ibm.biz/icpfs39install. License By installing this product you accept the license terms https://ibm.biz/icpfs39license + - description: OperandRegistry is the Schema for the operandregistries API. Documentation + For additional details regarding install parameters check https://ibm.biz/icpfs39install. + License By installing this product you accept the license terms https://ibm.biz/icpfs39license displayName: OperandRegistry kind: OperandRegistry name: operandregistries.operator.ibm.com @@ -72,7 +87,9 @@ spec: x-descriptors: - urn:alm:descriptor:io.kubernetes.phase version: v1alpha1 - - description: OperandRequest is the Schema for the operandrequests API. Documentation For additional details regarding install parameters check https://ibm.biz/icpfs39install. License By installing this product you accept the license terms https://ibm.biz/icpfs39license + - description: OperandRequest is the Schema for the operandrequests API. Documentation + For additional details regarding install parameters check https://ibm.biz/icpfs39install. + License By installing this product you accept the license terms https://ibm.biz/icpfs39license displayName: OperandRequest kind: OperandRequest name: operandrequests.operator.ibm.com @@ -92,6 +109,13 @@ spec: x-descriptors: - urn:alm:descriptor:io.kubernetes.phase version: v1alpha1 + - description: OperatorConfig is the Schema for the operatorconfigs API. Documentation + For additional details regarding install parameters check https://ibm.biz/icpfs39install. + License By installing this product you accept the license terms https://ibm.biz/icpfs39license + displayName: OperatorConfig + kind: OperatorConfig + name: operatorconfigs.operator.ibm.com + version: v1alpha1 description: |- # Introduction @@ -465,6 +489,6 @@ spec: provider: name: IBM relatedImages: - - image: icr.io/cpopen/odlm + - image: icr.io/cpopen/odlm:4.4.0 name: ODLM_IMAGE version: 0.0.0 diff --git a/config/rbac/operatorconfig_editor_role.yaml b/config/rbac/operatorconfig_editor_role.yaml new file mode 100644 index 00000000..17a6e034 --- /dev/null +++ b/config/rbac/operatorconfig_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit operatorconfigs. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: operatorconfig-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: operand-deployment-lifecycle-manager + app.kubernetes.io/part-of: operand-deployment-lifecycle-manager + app.kubernetes.io/managed-by: kustomize + name: operatorconfig-editor-role +rules: +- apiGroups: + - operator.ibm.com + resources: + - operatorconfigs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - operator.ibm.com + resources: + - operatorconfigs/status + verbs: + - get diff --git a/config/rbac/operatorconfig_viewer_role.yaml b/config/rbac/operatorconfig_viewer_role.yaml new file mode 100644 index 00000000..a785595c --- /dev/null +++ b/config/rbac/operatorconfig_viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view operatorconfigs. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: operatorconfig-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: operand-deployment-lifecycle-manager + app.kubernetes.io/part-of: operand-deployment-lifecycle-manager + app.kubernetes.io/managed-by: kustomize + name: operatorconfig-viewer-role +rules: +- apiGroups: + - operator.ibm.com + resources: + - operatorconfigs + verbs: + - get + - list + - watch +- apiGroups: + - operator.ibm.com + resources: + - operatorconfigs/status + verbs: + - get diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 98a17f84..a3017386 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -1,11 +1,51 @@ - +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: operand-deployment-lifecycle-manager +rules: +- apiGroups: + - apiextensions.k8s.io + resources: + - customresourcedefinitions + verbs: + - get +- apiGroups: + - operator.ibm.com + resources: + - auditloggings + - certmanagers + verbs: + - delete + - get +- apiGroups: + - operators.coreos.com + resources: + - catalogsources + verbs: + - get --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: - creationTimestamp: null name: operand-deployment-lifecycle-manager + namespace: placeholder rules: +- apiGroups: + - "" + resources: + - configmaps + - namespaces + - secrets + - services + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - '*' resources: @@ -18,3 +58,134 @@ rules: - patch - update - watch +- apiGroups: + - k8s.keycloak.org + resources: + - keycloakrealmimports + - keycloaks + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - operator.ibm.com + resources: + - operandbindinfos + - operandbindinfos/finalizers + - operandbindinfos/status + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - operator.ibm.com + resources: + - operandconfigs + - operandconfigs/finalizers + - operandconfigs/status + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - operator.ibm.com + resources: + - operandregistries + - operandregistries/finalizers + - operandregistries/status + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - operator.ibm.com + resources: + - operandrequests + - operandrequests/finalizers + - operandrequests/status + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - operator.ibm.com + resources: + - operatorconfigs + - operatorconfigs/finalizers + - operatorconfigs/status + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - operators.coreos.com + resources: + - clusterserviceversions + - subscriptions + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - operators.coreos.com + resources: + - installplans + - operatorgroups + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - packages.operators.coreos.com + resources: + - packagemanifests + verbs: + - get + - list + - patch + - update + - watch +- apiGroups: + - route.openshift.io + resources: + - routes + verbs: + - create + - delete + - get + - list + - patch + - update + - watch diff --git a/config/rbac/role_binding.yaml b/config/rbac/role_binding.yaml index c24c70c9..1b48546b 100644 --- a/config/rbac/role_binding.yaml +++ b/config/rbac/role_binding.yaml @@ -1,4 +1,17 @@ apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: operand-deployment-lifecycle-manager +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: operand-deployment-lifecycle-manager +subjects: +- kind: ServiceAccount + name: operand-deployment-lifecycle-manager + namespace: system +--- +apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: operand-deployment-lifecycle-manager diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index c32ed67f..c488daf4 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -3,3 +3,4 @@ resources: - operator_v1alpha1_operandrequest.yaml - operator_v1alpha1_operandregistry.yaml - operator_v1alpha1_operandconfig.yaml +- operator_v1alpha1_operandbindinfo.yaml diff --git a/config/samples/operator_v1alpha1_operandbindinfo.yaml b/config/samples/operator_v1alpha1_operandbindinfo.yaml new file mode 100644 index 00000000..5b1c634b --- /dev/null +++ b/config/samples/operator_v1alpha1_operandbindinfo.yaml @@ -0,0 +1,16 @@ +apiVersion: operator.ibm.com/v1alpha1 +kind: OperandBindInfo +metadata: + labels: + app.kubernetes.io/instance: "operand-deployment-lifecycle-manager" + app.kubernetes.io/managed-by: "operand-deployment-lifecycle-manager" + app.kubernetes.io/name: "operand-deployment-lifecycle-manager" + name: example-service +spec: + bindings: + public: + secret: mongodb-secret + configmap: mongodb-configmap + description: Binding information that should be accessible to MongoDB adopters + operand: mongodb-atlas-kubernetes + registry: example-service diff --git a/config/samples/operator_v1alpha1_operandconfig.yaml b/config/samples/operator_v1alpha1_operandconfig.yaml index c2aab7eb..539cb40a 100644 --- a/config/samples/operator_v1alpha1_operandconfig.yaml +++ b/config/samples/operator_v1alpha1_operandconfig.yaml @@ -8,12 +8,15 @@ metadata: name: example-service spec: services: - - name: etcd + - name: jaeger spec: - etcdCluster: - size: 1 - - name: jenkins + jaeger: + strategy: allinone + - name: mongodb-atlas-kubernetes spec: - jenkins: - service: - port: 8081 + atlasDeployment: + deploymentSpec: + name: test-deployment + projectRef: + name: my-project + diff --git a/config/samples/operator_v1alpha1_operandregistry.yaml b/config/samples/operator_v1alpha1_operandregistry.yaml index 77299701..77bd1fe6 100644 --- a/config/samples/operator_v1alpha1_operandregistry.yaml +++ b/config/samples/operator_v1alpha1_operandregistry.yaml @@ -8,16 +8,16 @@ metadata: name: example-service spec: operators: - - name: etcd - namespace: etcd-cluster-operator - channel: clusterwide-alpha + - name: jaeger + namespace: default + channel: stable installMode: cluster - packageName: etcd + packageName: jaeger sourceName: community-operators sourceNamespace: openshift-marketplace - - name: jenkins + - name: mongodb-atlas-kubernetes namespace: default - channel: alpha - packageName: jenkins-operator + channel: stable + packageName: mongodb-atlas-kubernetes sourceName: community-operators sourceNamespace: openshift-marketplace diff --git a/config/samples/operator_v1alpha1_operandrequest.yaml b/config/samples/operator_v1alpha1_operandrequest.yaml index 987825a1..9cf20f87 100644 --- a/config/samples/operator_v1alpha1_operandrequest.yaml +++ b/config/samples/operator_v1alpha1_operandrequest.yaml @@ -10,5 +10,5 @@ spec: requests: - registry: example-service operands: - - name: etcd - - name: jenkins + - name: jaeger + - name: mongodb-atlas-kubernetes diff --git a/config/samples/operator_v1alpha1_operatorconfig.yaml b/config/samples/operator_v1alpha1_operatorconfig.yaml new file mode 100644 index 00000000..f57b5d62 --- /dev/null +++ b/config/samples/operator_v1alpha1_operatorconfig.yaml @@ -0,0 +1,12 @@ +apiVersion: operator.ibm.com/v1alpha1 +kind: OperatorConfig +metadata: + labels: + app.kubernetes.io/name: operatorconfig + app.kubernetes.io/instance: operatorconfig-sample + app.kubernetes.io/part-of: operand-deployment-lifecycle-manager + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: operand-deployment-lifecycle-manager + name: operatorconfig-sample +spec: + # TODO(user): Add fields here diff --git a/controllers/constant/constant.go b/controllers/constant/constant.go index cb536c94..3015495b 100644 --- a/controllers/constant/constant.go +++ b/controllers/constant/constant.go @@ -28,9 +28,21 @@ const ( //NotUninstallLabel is the label used to prevent subscription/CR from uninstall NotUninstallLabel string = "operator.ibm.com/opreq-do-not-uninstall" - //OpreqLabel is the label used to label the subscription/CR managed by ODLM + //OpreqLabel is the label used to label the Subscription/CR/Configmap managed by ODLM OpreqLabel string = "operator.ibm.com/opreq-control" + //OpreqTrackerLabel is the label used to label the OperandRequest Configmap managed by ODLM + OpreqTrackerLabel string = "operator.ibm.com/operand-request-tracker" + + //InternalOpreqLabel is the label used label the OperandRequest internally created by ODLM + OperandOnlyLabel string = "operator.ibm.com/operand-only" + + //ODLMReferenceAnnotation is the annotation used to annotate the resources used for ODLM operand value reference + ODLMReferenceAnnotation string = "operator.ibm.com/referenced-by-odlm-resource" + + //ODLMWatchedLabel is the label used to label the resources watched by ODLM for value reference + ODLMWatchedLabel string = "operator.ibm.com/watched-by-odlm" + //OpbiNsLabel is the label used to add OperandBindInfo namespace to the secrets/configmaps watched by ODLM OpbiNsLabel string = "operator.ibm.com/watched-by-opbi-with-namespace" @@ -55,6 +67,12 @@ const ( //HashedData is the key for checking the checksum of data section HashedData string = "hashedData" + //HashedData is the key for k8s Resource + K8sHashedData string = "operator.ibm.com/operand-depoyment-lifecycle-manager.hashedData" + + //RouteHash is the key for hash value of route + RouteHash string = "operator.ibm.com/odlm.route.hashedData" + //DefaultRequestTimeout is the default timeout for kube request DefaultRequestTimeout = 5 * time.Second @@ -81,4 +99,10 @@ const ( //DefaultCSVWaitPeriod is the default period for wait CSV ready DefaultCSVWaitPeriod = 1 * time.Minute + + //DefaultCRRetryNumber is the default maximum number of retry for reconciling a custom resource + DefaultCRRetryNumber = 3 + + //StatusMonitoredServices is the annotation key for monitored services + StatusMonitoredServices = "status-monitored-services" ) diff --git a/controllers/namespacescope/namespacescope_controler_test.go b/controllers/namespacescope/namespacescope_controler_test.go index f5893096..6be317e9 100644 --- a/controllers/namespacescope/namespacescope_controler_test.go +++ b/controllers/namespacescope/namespacescope_controler_test.go @@ -25,9 +25,9 @@ import ( nssv1 "github.com/IBM/ibm-namespace-scope-operator/api/v1" - operatorv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/api/v1alpha1" - "github.com/IBM/operand-deployment-lifecycle-manager/controllers/constant" - "github.com/IBM/operand-deployment-lifecycle-manager/controllers/testutil" + operatorv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/v4/api/v1alpha1" + "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/constant" + "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/testutil" ) // +kubebuilder:docs-gen:collapse=Imports diff --git a/controllers/namespacescope/namespacescope_controller.go b/controllers/namespacescope/namespacescope_controller.go index 72fffdac..6d34e4f0 100644 --- a/controllers/namespacescope/namespacescope_controller.go +++ b/controllers/namespacescope/namespacescope_controller.go @@ -33,10 +33,10 @@ import ( nssv1 "github.com/IBM/ibm-namespace-scope-operator/api/v1" - operatorv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/api/v1alpha1" - "github.com/IBM/operand-deployment-lifecycle-manager/controllers/constant" - deploy "github.com/IBM/operand-deployment-lifecycle-manager/controllers/operator" - "github.com/IBM/operand-deployment-lifecycle-manager/controllers/util" + operatorv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/v4/api/v1alpha1" + "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/constant" + deploy "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/operator" + "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/util" ) var ( diff --git a/controllers/namespacescope/namespacescope_suite_test.go b/controllers/namespacescope/namespacescope_suite_test.go index 9246ce58..fe72a86a 100644 --- a/controllers/namespacescope/namespacescope_suite_test.go +++ b/controllers/namespacescope/namespacescope_suite_test.go @@ -22,7 +22,7 @@ import ( "testing" "time" - etcdv1beta2 "github.com/coreos/etcd-operator/pkg/apis/etcd/v1beta2" + jaegerv1 "github.com/jaegertracing/jaeger-operator/apis/v1" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/onsi/gomega/gexec" @@ -33,16 +33,15 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/envtest" - "sigs.k8s.io/controller-runtime/pkg/envtest/printer" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" nssv1 "github.com/IBM/ibm-namespace-scope-operator/api/v1" - apiv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/api/v1alpha1" - "github.com/IBM/operand-deployment-lifecycle-manager/controllers/operandregistry" - "github.com/IBM/operand-deployment-lifecycle-manager/controllers/operandrequest" - deploy "github.com/IBM/operand-deployment-lifecycle-manager/controllers/operator" + apiv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/v4/api/v1alpha1" + "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/operandregistry" + "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/operandrequest" + deploy "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/operator" // +kubebuilder:scaffold:imports ) @@ -64,9 +63,8 @@ var ( func TestNamespaceScope(t *testing.T) { RegisterFailHandler(Fail) - RunSpecsWithDefaultAndCustomReporters(t, - "NamespaceScope Controller Suite", - []Reporter{printer.NewlineReporter{}}) + RunSpecs(t, + "NamespaceScope Controller Suite") } var _ = BeforeSuite(func(done Done) { @@ -94,7 +92,7 @@ var _ = BeforeSuite(func(done Done) { Expect(err).NotTo(HaveOccurred()) err = olmv1.AddToScheme(clientgoscheme.Scheme) Expect(err).NotTo(HaveOccurred()) - err = etcdv1beta2.AddToScheme(clientgoscheme.Scheme) + err = jaegerv1.AddToScheme(clientgoscheme.Scheme) Expect(err).NotTo(HaveOccurred()) k8sClient, err = client.New(cfg, client.Options{Scheme: clientgoscheme.Scheme}) diff --git a/controllers/operandbindinfo/operandbindinfo_controller.go b/controllers/operandbindinfo/operandbindinfo_controller.go index 1d2cc6e9..10f2d964 100644 --- a/controllers/operandbindinfo/operandbindinfo_controller.go +++ b/controllers/operandbindinfo/operandbindinfo_controller.go @@ -24,18 +24,24 @@ import ( "strings" "time" + ocproute "github.com/openshift/api/route/v1" "github.com/pkg/errors" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/equality" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" utilerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/client-go/discovery" + "k8s.io/client-go/rest" "k8s.io/klog" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/config" + "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/event" "sigs.k8s.io/controller-runtime/pkg/handler" @@ -44,23 +50,28 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/source" - operatorv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/api/v1alpha1" - "github.com/IBM/operand-deployment-lifecycle-manager/controllers/constant" - deploy "github.com/IBM/operand-deployment-lifecycle-manager/controllers/operator" - "github.com/IBM/operand-deployment-lifecycle-manager/controllers/util" + operatorv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/v4/api/v1alpha1" + "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/constant" + deploy "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/operator" + "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/util" ) // Reconciler reconciles a OperandBindInfo object type Reconciler struct { *deploy.ODLMOperator + Config *rest.Config } var ( publicPrefix, _ = regexp.Compile(`^public(.*)$`) privatePrefix, _ = regexp.Compile(`^private(.*)$`) protectedPrefix, _ = regexp.Compile(`^protected(.*)$`) + routeGroupVersion = "route.openshift.io/v1" + isRouteAPI = false ) +// +kubebuilder:rbac:groups=operator.ibm.com,namespace="placeholder",resources=operandbindinfos;operandbindinfos/status;operandbindinfos/finalizers,verbs=get;list;watch;create;update;patch;delete + // Reconcile reads that state of the cluster for a OperandBindInfo object and makes changes based on the state read // and what is in the OperandBindInfo.Spec // Note: @@ -88,6 +99,12 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Re klog.V(1).Infof("Reconciling OperandBindInfo: %s", req.NamespacedName) + if isRouteAPI { + klog.V(2).Info("Route API enabled") + } else { + klog.Info("Route API disabled") + } + // If the finalizer is added, EnsureFinalizer() will return true. If the finalizer is already there, EnsureFinalizer() will return false if bindInfoInstance.EnsureFinalizer() { err := r.Patch(ctx, bindInfoInstance, client.MergeFrom(originalInstance)) @@ -142,11 +159,11 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Re return ctrl.Result{}, nil } // Get the operand namespace - operandOperator := registryInstance.GetOperator(bindInfoInstance.Spec.Operand) - if operandOperator == nil { - klog.Errorf("failed to find operator %s in the OperandRegistry %s", bindInfoInstance.Spec.Operand, registryInstance.Name) + operandOperator, err := r.GetOperandFromRegistry(ctx, registryInstance, bindInfoInstance.Spec.Operand) + if err != nil || operandOperator == nil { + klog.Errorf("failed to find operator %s in the OperandRegistry %s: %v", bindInfoInstance.Spec.Operand, registryInstance.Name, err) r.Recorder.Eventf(bindInfoInstance, corev1.EventTypeWarning, "NotFound", "NotFound operator %s in the OperandRegistry %s", bindInfoInstance.Spec.Operand, registryInstance.Name) - return ctrl.Result{}, nil + return ctrl.Result{}, err } operandNamespace := registryInstance.Namespace @@ -164,6 +181,9 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Re } merr.Add(err) continue + } else if !requestInstance.ObjectMeta.DeletionTimestamp.IsZero() { + klog.Warningf("OperandRequest %s/%s is being deleted, skip the OperandBindInfo %s/%s", requestInstance.Namespace, requestInstance.Name, bindInfoInstance.Namespace, bindInfoInstance.Name) + continue } // Get binding information from OperandRequest secretReq, cmReq := getBindingInfofromRequest(bindInfoInstance, requestInstance) @@ -194,6 +214,27 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Re continue } requeue = requeue || requeueCm + + if isRouteAPI { + // Copy Route data into configmap and share configmap + if binding.Route != nil { + requeueRoute, err := r.copyRoute(ctx, *binding.Route, "", operandNamespace, bindRequest.Namespace, key, bindInfoInstance, requestInstance) + if err != nil { + merr.Add(err) + continue + } + requeue = requeue || requeueRoute + } + } + // Copy Service data into configmap and share configmap + if binding.Service != nil { + requeueService, err := r.copyService(ctx, *binding.Service, "", operandNamespace, bindRequest.Namespace, key, bindInfoInstance, requestInstance) + if err != nil { + merr.Add(err) + continue + } + requeue = requeue || requeueService + } } } if len(merr.Errors) != 0 { @@ -249,6 +290,7 @@ func (r *Reconciler) copySecret(ctx context.Context, sourceName, targetName, sou } secretLabel[bindInfoInstance.Namespace+"."+bindInfoInstance.Name+"/bindinfo"] = "true" secretLabel[constant.OpbiTypeLabel] = "copy" + secretLabel[constant.ODLMWatchedLabel] = "true" secretCopy := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: targetName, @@ -259,8 +301,8 @@ func (r *Reconciler) copySecret(ctx context.Context, sourceName, targetName, sou Data: secret.Data, StringData: secret.StringData, } - // Set the OperandRequest as the controller of the Secret - if err := controllerutil.SetControllerReference(requestInstance, secretCopy, r.Scheme); err != nil { + // Set the OperandRequest as the owner of the Secret + if err := controllerutil.SetOwnerReference(requestInstance, secretCopy, r.Scheme); err != nil { return false, errors.Wrapf(err, "failed to set OperandRequest %s as the owner of Secret %s", requestInstance.Name, targetName) } @@ -273,7 +315,15 @@ func (r *Reconciler) copySecret(ctx context.Context, sourceName, targetName, sou if err := r.Client.Get(ctx, types.NamespacedName{Namespace: targetNs, Name: targetName}, existingSecret); err != nil { return false, errors.Wrapf(err, "failed to get secret %s/%s", targetNs, targetName) } - if needUpdate := compareSecret(secretCopy, existingSecret); needUpdate { + + // Copy the owner reference from the existing secret to the new secret + secretCopy.SetOwnerReferences(existingSecret.GetOwnerReferences()) + // Set the OperandRequest as the owner of the new Secret + if err := controllerutil.SetOwnerReference(requestInstance, secretCopy, r.Scheme); err != nil { + return false, errors.Wrapf(err, "failed to set OperandRequest %s as the owner of Secret %s", requestInstance.Name, targetName) + } + + if needUpdate := util.CompareSecret(secretCopy, existingSecret); needUpdate { podRefreshment = true if err := r.Update(ctx, secretCopy); err != nil { return false, errors.Wrapf(err, "failed to update secret %s/%s", targetNs, targetName) @@ -289,10 +339,10 @@ func (r *Reconciler) copySecret(ctx context.Context, sourceName, targetName, sou } } - ensureLabelsForSecret(secret, map[string]string{ - constant.OpbiNsLabel: bindInfoInstance.Namespace, - constant.OpbiNameLabel: bindInfoInstance.Name, - constant.OpbiTypeLabel: "original", + util.EnsureLabelsForSecret(secret, map[string]string{ + bindInfoInstance.Namespace + "." + bindInfoInstance.Name + "/bindinfo": "true", + constant.OpbiTypeLabel: "original", + constant.ODLMWatchedLabel: "true", }) // Update the operand Secret @@ -342,6 +392,7 @@ func (r *Reconciler) copyConfigmap(ctx context.Context, sourceName, targetName, } cmLabel[bindInfoInstance.Namespace+"."+bindInfoInstance.Name+"/bindinfo"] = "true" cmLabel[constant.OpbiTypeLabel] = "copy" + cmLabel[constant.ODLMWatchedLabel] = "true" cmCopy := &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: targetName, @@ -351,8 +402,8 @@ func (r *Reconciler) copyConfigmap(ctx context.Context, sourceName, targetName, Data: cm.Data, BinaryData: cm.BinaryData, } - // Set the OperandRequest as the controller of the configmap - if err := controllerutil.SetControllerReference(requestInstance, cmCopy, r.Scheme); err != nil { + // Set OperandRequest as the owners of the configmap + if err := controllerutil.SetOwnerReference(requestInstance, cmCopy, r.Scheme); err != nil { return false, errors.Wrapf(err, "failed to set OperandRequest %s as the owner of ConfigMap %s", requestInstance.Name, sourceName) } @@ -365,7 +416,15 @@ func (r *Reconciler) copyConfigmap(ctx context.Context, sourceName, targetName, if err := r.Client.Get(ctx, types.NamespacedName{Namespace: targetNs, Name: targetName}, existingCm); err != nil { return false, errors.Wrapf(err, "failed to get ConfigMap %s/%s", targetNs, targetName) } - if needUpdate := compareConfigMap(cmCopy, existingCm); needUpdate { + + // Copy the owner reference from the existing secret to the new configmap + cmCopy.SetOwnerReferences(existingCm.GetOwnerReferences()) + // Set the OperandRequest as the owner of the new configmap + if err := controllerutil.SetOwnerReference(requestInstance, cmCopy, r.Scheme); err != nil { + return false, errors.Wrapf(err, "failed to set OperandRequest %s as the owner of ConfigMap %s", requestInstance.Name, targetName) + } + + if needUpdate := util.CompareConfigMap(cmCopy, existingCm); needUpdate { podRefreshment = true if err := r.Update(ctx, cmCopy); err != nil { return false, errors.Wrapf(err, "failed to update ConfigMap %s/%s", targetNs, sourceName) @@ -383,10 +442,10 @@ func (r *Reconciler) copyConfigmap(ctx context.Context, sourceName, targetName, } // Set the OperandBindInfo label for the ConfigMap - ensureLabelsForConfigMap(cm, map[string]string{ - constant.OpbiNsLabel: bindInfoInstance.Namespace, - constant.OpbiNameLabel: bindInfoInstance.Name, - constant.OpbiTypeLabel: "original", + util.EnsureLabelsForConfigMap(cm, map[string]string{ + bindInfoInstance.Namespace + "." + bindInfoInstance.Name + "/bindinfo": "true", + constant.OpbiTypeLabel: "original", + constant.ODLMWatchedLabel: "true", }) // Update the operand Configmap @@ -398,6 +457,290 @@ func (r *Reconciler) copyConfigmap(ctx context.Context, sourceName, targetName, return false, nil } +// copyRoute reads the data map and copies OCP Route data as specified by the +// field path in the data map values +func (r *Reconciler) copyRoute(ctx context.Context, route operatorv1alpha1.Route, targetName, sourceNs, targetNs, key string, + bindInfoInstance *operatorv1alpha1.OperandBindInfo, requestInstance *operatorv1alpha1.OperandRequest) (requeue bool, err error) { + if route.Name == "" || sourceNs == "" || targetNs == "" { + return false, nil + } + + if route.Name == targetName && sourceNs == targetNs { + return false, nil + } + + if targetName == "" { + if publicPrefix.MatchString(key) { + targetName = bindInfoInstance.Name + "-" + route.Name + } else { + return false, nil + } + } + + sourceRoute := &ocproute.Route{} + if err := r.Client.Get(ctx, types.NamespacedName{Name: route.Name, Namespace: sourceNs}, sourceRoute); err != nil { + if apierrors.IsNotFound(err) { + klog.V(3).Infof("Route %s/%s is not found", sourceNs, route.Name) + r.Recorder.Eventf(bindInfoInstance, corev1.EventTypeNormal, "NotFound", "No Route %s in the namespace %s", route.Name, sourceNs) + return true, nil + } + return false, errors.Wrapf(err, "failed to get Route %s/%s", sourceNs, route.Name) + } + // Create the ConfigMap to the OperandRequest namespace + labels := make(map[string]string) + // Copy from the original labels to the target labels + for k, v := range sourceRoute.Labels { + labels[k] = v + } + labels[bindInfoInstance.Namespace+"."+bindInfoInstance.Name+"/bindinfo"] = "true" + labels[constant.OpbiTypeLabel] = "copy" + + sanitizedData, err := sanitizeOdlmRouteData(route.Data, sourceRoute.Spec) + if err != nil { + return false, err + } + cmCopy := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: targetName, + Namespace: targetNs, + Labels: labels, + }, + Data: sanitizedData, + } + // Set the OperandRequest as the controller of the configmap + if err := controllerutil.SetControllerReference(requestInstance, cmCopy, r.Scheme); err != nil { + return false, errors.Wrapf(err, "failed to set OperandRequest %s as the owner of ConfigMap %s", requestInstance.Name, route.Name) + } + + var podRefreshment bool + // Create the ConfigMap in the OperandRequest namespace + if err := r.Create(ctx, cmCopy); err != nil { + if apierrors.IsAlreadyExists(err) { + // If already exist, update the ConfigMap + existingCm := &corev1.ConfigMap{} + if err := r.Client.Get(ctx, types.NamespacedName{Namespace: targetNs, Name: targetName}, existingCm); err != nil { + return false, errors.Wrapf(err, "failed to get ConfigMap %s/%s", targetNs, targetName) + } + if needUpdate := util.CompareConfigMap(cmCopy, existingCm); needUpdate { + podRefreshment = true + if err := r.Update(ctx, cmCopy); err != nil { + return false, errors.Wrapf(err, "failed to update ConfigMap %s/%s", targetNs, route.Name) + } + } + } else { + return false, errors.Wrapf(err, "failed to create ConfigMap %s/%s", targetNs, route.Name) + } + } + + if podRefreshment { + if err := r.refreshPods(targetNs, targetName, "configmap"); err != nil { + return false, errors.Wrapf(err, "failed to refresh pods mounting ConfigMap %s/%s", targetNs, targetName) + } + } + + // Set the OperandBindInfo label for the ConfigMap + util.EnsureLabelsForRoute(sourceRoute, map[string]string{ + bindInfoInstance.Namespace + "." + bindInfoInstance.Name + "/bindinfo": "true", + constant.OpbiTypeLabel: "original", + }) + + // Update the operand Configmap + if err := r.Update(ctx, sourceRoute); err != nil { + return false, errors.Wrapf(err, "failed to update ConfigMap %s/%s", sourceRoute.Namespace, sourceRoute.Name) + } + klog.V(1).Infof("Route %s is copied from the namespace %s to the namespace %s", route.Name, sourceNs, targetNs) + + return false, nil +} + +// copyRoute reads the data map and copies K8s Service data as specified by the +// field path in the data map values +func (r *Reconciler) copyService(ctx context.Context, service operatorv1alpha1.ServiceData, targetName, sourceNs, targetNs, key string, + bindInfoInstance *operatorv1alpha1.OperandBindInfo, requestInstance *operatorv1alpha1.OperandRequest) (requeue bool, err error) { + if service.Name == "" || sourceNs == "" || targetNs == "" { + return false, nil + } + + if service.Name == targetName && sourceNs == targetNs { + return false, nil + } + + if targetName == "" { + if publicPrefix.MatchString(key) { + targetName = bindInfoInstance.Name + "-" + service.Name + } else { + return false, nil + } + } + + sourceService := &corev1.Service{} + if err := r.Client.Get(ctx, types.NamespacedName{Name: service.Name, Namespace: sourceNs}, sourceService); err != nil { + if apierrors.IsNotFound(err) { + klog.V(3).Infof("Route %s/%s is not found", sourceNs, service.Name) + r.Recorder.Eventf(bindInfoInstance, corev1.EventTypeNormal, "NotFound", "No Service %s in the namespace %s", service.Name, sourceNs) + return true, nil + } + return false, errors.Wrapf(err, "failed to get Service %s/%s", sourceNs, service.Name) + } + // Create the ConfigMap to the OperandRequest namespace + labels := make(map[string]string) + // Copy from the original labels to the target labels + for k, v := range sourceService.Labels { + labels[k] = v + } + labels[bindInfoInstance.Namespace+"."+bindInfoInstance.Name+"/bindinfo"] = "true" + labels[constant.OpbiTypeLabel] = "copy" + + sanitizedData, err := sanitizeServiceData(service.Data, *sourceService) + if err != nil { + return false, err + } + cmCopy := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: targetName, + Namespace: targetNs, + Labels: labels, + }, + Data: sanitizedData, + } + // Set the OperandRequest as the controller of the configmap + if err := controllerutil.SetControllerReference(requestInstance, cmCopy, r.Scheme); err != nil { + return false, errors.Wrapf(err, "failed to set OperandRequest %s as the owner of ConfigMap %s", requestInstance.Name, service.Name) + } + + var podRefreshment bool + // Create the ConfigMap in the OperandRequest namespace + if err := r.Create(ctx, cmCopy); err != nil { + if apierrors.IsAlreadyExists(err) { + // If already exist, update the ConfigMap + existingCm := &corev1.ConfigMap{} + if err := r.Client.Get(ctx, types.NamespacedName{Namespace: targetNs, Name: targetName}, existingCm); err != nil { + return false, errors.Wrapf(err, "failed to get ConfigMap %s/%s", targetNs, targetName) + } + if needUpdate := util.CompareConfigMap(cmCopy, existingCm); needUpdate { + podRefreshment = true + if err := r.Update(ctx, cmCopy); err != nil { + return false, errors.Wrapf(err, "failed to update ConfigMap %s/%s", targetNs, service.Name) + } + } + } else { + return false, errors.Wrapf(err, "failed to create ConfigMap %s/%s", targetNs, service.Name) + } + } + + if podRefreshment { + if err := r.refreshPods(targetNs, targetName, "configmap"); err != nil { + return false, errors.Wrapf(err, "failed to refresh pods mounting ConfigMap %s/%s", targetNs, targetName) + } + } + + // Set the OperandBindInfo label for the ConfigMap + util.EnsureLabelsForService(sourceService, map[string]string{ + bindInfoInstance.Namespace + "." + bindInfoInstance.Name + "/bindinfo": "true", + constant.OpbiTypeLabel: "original", + }) + + // Update the operand Configmap + if err := r.Update(ctx, sourceService); err != nil { + return false, errors.Wrapf(err, "failed to update ConfigMap %s/%s", sourceService.Namespace, sourceService.Name) + } + klog.V(1).Infof("Service %s is copied from the namespace %s to the namespace %s", service.Name, sourceNs, targetNs) + + return false, nil +} + +// sanitizeOdlmRouteData takes a map, i.e. ODLM's Route.Data, and an OCP Route.Spec, +// and returns a map ready to be included into a ConfigMap's data. The ODLM's +// Route.Data is sanitized because the values are YAML path references +// in map because they correspond to YAML fields in a OCP Route. Ensures that: +// 1. the field actually exists, otherwise returns an error +// 2. extracts the value from the OCP Route's field, the value must be a basic +// type, which includes: int, float, bool, and strings. Anything else and +// an error is returned +func sanitizeOdlmRouteData(m map[string]string, route ocproute.RouteSpec) (map[string]string, error) { + sanitized := make(map[string]string, len(m)) + for k, v := range m { + fields := strings.Split(v, ".") + fields = fields[1:] + if fields[0] != "spec" { + return nil, errors.Errorf("Bindable Route.Data must only reference values from OCP Route.Spec") + } + fields = fields[1:] + + content, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&route) + if err != nil { + return nil, err + } + newUnstr := &unstructured.Unstructured{} + newUnstr.SetUnstructuredContent(content) + + trueValue, isExists, err := nestedBasicType(newUnstr.Object, fields...) + if err != nil { + return nil, err + } + if !isExists { + return nil, errors.Errorf("Bindable Route.Data references a field that does not exist: %s", v) + } + + sanitized[k] = trueValue + } + return sanitized, nil +} + +// sanitizeServiceData takes a map, i.e. ODLM's Service.Data, and a K8s Service object +// and returns a map ready to be included into a ConfigMap's data. The ODLM's +// Service.Data is sanitized because the values are YAML fields in a K8s Service. +// Ensures that: +// 1. the field actually exists, otherwise returns an error +// 2. extracts the value from the K8s Service's field, the value will be +// stringified +func sanitizeServiceData(m map[string]string, service corev1.Service) (map[string]string, error) { + sanitized := make(map[string]string, len(m)) + for k, v := range m { + trueValue, err := util.SanitizeObjectString(v, service) + if err != nil { + return nil, err + } + sanitized[k] = trueValue + } + return sanitized, nil +} + +// nestedBasicType is a wrapper around the various unstructured.Nested methods +// used to extract values from nested fields. It takes the same arguments +// as the Nested methods and returns a string if some basic type value is found. +// Otherwise the bool and error values from the Nested functions will be +// returned. +// Basic types include: string, int64, float64, and bool +func nestedBasicType(obj map[string]interface{}, fields ...string) (string, bool, error) { + typeTest, isExists, err := unstructured.NestedFieldNoCopy(obj, fields...) + if err != nil { + return "", isExists, err + } + if !isExists { + return "", isExists, err + } + + switch fieldType := reflect.TypeOf(typeTest); fieldType.String() { + case "string": + return unstructured.NestedString(obj, fields...) + case "int", "int32", "int64": + var value int64 + value, isExists, err = unstructured.NestedInt64(obj, fields...) + return fmt.Sprintf("%d", value), isExists, err + case "float32", "float64": + var value float64 + value, isExists, err = unstructured.NestedFloat64(obj, fields...) + return fmt.Sprintf("%f", value), isExists, err + case "bool": + var value bool + value, isExists, err = unstructured.NestedBool(obj, fields...) + return fmt.Sprintf("%t", value), isExists, err + default: + return "", false, errors.Errorf("Path reference does not lead to an int, float, bool, or string: .spec.%s", strings.Join(fields, ".")) + } +} + func (r *Reconciler) cleanupCopies(ctx context.Context, bindInfoInstance *operatorv1alpha1.OperandBindInfo) error { secretList := &corev1.SecretList{} cmList := &corev1.ConfigMapList{} @@ -537,11 +880,15 @@ func unique(stringSlice []string) []string { func toOpbiRequest() handler.MapFunc { return func(object client.Object) []reconcile.Request { opbiInstance := []reconcile.Request{} - lables := object.GetLabels() - name, nameOk := lables[constant.OpbiNameLabel] - ns, namespaceOK := lables[constant.OpbiNsLabel] - if nameOk && namespaceOK { - opbiInstance = append(opbiInstance, reconcile.Request{NamespacedName: types.NamespacedName{Name: name, Namespace: ns}}) + labels := object.GetLabels() + reg, _ := regexp.Compile(`^(.*)\.(.*)\/bindinfo`) + for annotation := range labels { + if reg.MatchString(annotation) { + annotationSlices := strings.Split(annotation, ".") + bindinfoNamespace := annotationSlices[0] + bindinfoName := strings.Split(annotationSlices[1], "/")[0] + opbiInstance = append(opbiInstance, reconcile.Request{NamespacedName: types.NamespacedName{Name: bindinfoName, Namespace: bindinfoNamespace}}) + } } return opbiInstance } @@ -697,7 +1044,7 @@ func (r *Reconciler) refreshPodsFromDaemonSet(ns, name, resourceType string) err // SetupWithManager adds OperandBindInfo controller to the manager. func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error { - cmSecretPredicates := predicate.Funcs{ + bindablePredicates := predicate.Funcs{ CreateFunc: func(e event.CreateEvent) bool { labels := e.Object.GetLabels() for labelKey, labelValue := range labels { @@ -738,17 +1085,41 @@ func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error { }, } - return ctrl.NewControllerManagedBy(mgr). + options := controller.Options{ + MaxConcurrentReconciles: r.MaxConcurrentReconciles, // Set the desired value for max concurrent reconciles. + } + + var err error + if r.Config == nil { + r.Config, err = config.GetConfig() + if err != nil { + klog.Errorf("Failed to get config: %v", err) + return err + } + } + dc := discovery.NewDiscoveryClientForConfigOrDie(r.Config) + _, apiLists, err := dc.ServerGroupsAndResources() + if err != nil { + return err + } + for _, apiList := range apiLists { + if apiList.GroupVersion == routeGroupVersion { + isRouteAPI = true + } + } + + controller := ctrl.NewControllerManagedBy(mgr). + WithOptions(options). For(&operatorv1alpha1.OperandBindInfo{}). Watches( &source.Kind{Type: &corev1.ConfigMap{}}, handler.EnqueueRequestsFromMapFunc(toOpbiRequest()), - builder.WithPredicates(cmSecretPredicates), + builder.WithPredicates(bindablePredicates), ). Watches( &source.Kind{Type: &corev1.Secret{}}, handler.EnqueueRequestsFromMapFunc(toOpbiRequest()), - builder.WithPredicates(cmSecretPredicates), + builder.WithPredicates(bindablePredicates), ). Watches( &source.Kind{Type: &operatorv1alpha1.OperandRequest{}}, @@ -759,31 +1130,13 @@ func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error { &source.Kind{Type: &operatorv1alpha1.OperandRegistry{}}, handler.EnqueueRequestsFromMapFunc(r.getOperandRegistryToRequestMapper(mgr)), builder.WithPredicates(opregPredicates), - ).Complete(r) -} - -func ensureLabelsForSecret(secret *corev1.Secret, labels map[string]string) { - if secret.Labels == nil { - secret.Labels = make(map[string]string) - } - for k, v := range labels { - secret.Labels[k] = v - } -} - -func ensureLabelsForConfigMap(cm *corev1.ConfigMap, labels map[string]string) { - if cm.Labels == nil { - cm.Labels = make(map[string]string) - } - for k, v := range labels { - cm.Labels[k] = v + ) + if isRouteAPI { + controller.Watches( + &source.Kind{Type: &ocproute.Route{}}, + handler.EnqueueRequestsFromMapFunc(toOpbiRequest()), + builder.WithPredicates(bindablePredicates), + ) } -} - -func compareSecret(secret *corev1.Secret, existingSecret *corev1.Secret) (needUpdate bool) { - return !equality.Semantic.DeepEqual(secret.GetLabels(), existingSecret.GetLabels()) || !equality.Semantic.DeepEqual(secret.Type, existingSecret.Type) || !equality.Semantic.DeepEqual(secret.Data, existingSecret.Data) || !equality.Semantic.DeepEqual(secret.StringData, existingSecret.StringData) -} - -func compareConfigMap(configMap *corev1.ConfigMap, existingConfigMap *corev1.ConfigMap) (needUpdate bool) { - return !equality.Semantic.DeepEqual(configMap.GetLabels(), existingConfigMap.GetLabels()) || !equality.Semantic.DeepEqual(configMap.Data, existingConfigMap.Data) || !equality.Semantic.DeepEqual(configMap.BinaryData, existingConfigMap.BinaryData) + return controller.Complete(r) } diff --git a/controllers/operandbindinfo/operandbindinfo_controller_test.go b/controllers/operandbindinfo/operandbindinfo_controller_test.go index c8d94f98..493f2eca 100644 --- a/controllers/operandbindinfo/operandbindinfo_controller_test.go +++ b/controllers/operandbindinfo/operandbindinfo_controller_test.go @@ -25,8 +25,8 @@ import ( "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" - operatorv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/api/v1alpha1" - "github.com/IBM/operand-deployment-lifecycle-manager/controllers/testutil" + operatorv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/v4/api/v1alpha1" + "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/testutil" ) // +kubebuilder:docs-gen:collapse=Imports diff --git a/controllers/operandbindinfo/operandbindinfo_suite_test.go b/controllers/operandbindinfo/operandbindinfo_suite_test.go index 8fa02f95..be7a1ea6 100644 --- a/controllers/operandbindinfo/operandbindinfo_suite_test.go +++ b/controllers/operandbindinfo/operandbindinfo_suite_test.go @@ -22,26 +22,26 @@ import ( "testing" "time" - etcdv1beta2 "github.com/coreos/etcd-operator/pkg/apis/etcd/v1beta2" + jaegerv1 "github.com/jaegertracing/jaeger-operator/apis/v1" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/onsi/gomega/gexec" olmv1 "github.com/operator-framework/api/pkg/operators/v1" olmv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" + operatorsv1 "github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/apis/operators/v1" clientgoscheme "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/envtest" - "sigs.k8s.io/controller-runtime/pkg/envtest/printer" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" nssv1 "github.com/IBM/ibm-namespace-scope-operator/api/v1" - apiv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/api/v1alpha1" - "github.com/IBM/operand-deployment-lifecycle-manager/controllers/operandregistry" - "github.com/IBM/operand-deployment-lifecycle-manager/controllers/operandrequest" - deploy "github.com/IBM/operand-deployment-lifecycle-manager/controllers/operator" + apiv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/v4/api/v1alpha1" + "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/operandregistry" + "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/operandrequest" + deploy "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/operator" // +kubebuilder:scaffold:imports ) @@ -63,9 +63,8 @@ var ( func TestOperandBindInfo(t *testing.T) { RegisterFailHandler(Fail) - RunSpecsWithDefaultAndCustomReporters(t, - "OperandBindInfo Controller Suite", - []Reporter{printer.NewlineReporter{}}) + RunSpecs(t, + "OperandBindInfo Controller Suite") } var _ = BeforeSuite(func(done Done) { @@ -92,7 +91,9 @@ var _ = BeforeSuite(func(done Done) { Expect(err).NotTo(HaveOccurred()) err = olmv1.AddToScheme(clientgoscheme.Scheme) Expect(err).NotTo(HaveOccurred()) - err = etcdv1beta2.AddToScheme(clientgoscheme.Scheme) + err = jaegerv1.AddToScheme(clientgoscheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = operatorsv1.AddToScheme(clientgoscheme.Scheme) Expect(err).NotTo(HaveOccurred()) k8sClient, err = client.New(cfg, client.Options{Scheme: clientgoscheme.Scheme}) @@ -108,6 +109,7 @@ var _ = BeforeSuite(func(done Done) { // Setup Manager with OperandBindInfo Controller err = (&Reconciler{ + Config: cfg, ODLMOperator: deploy.NewODLMOperator(k8sManager, "OperandBindInfo"), }).SetupWithManager(k8sManager) Expect(err).ToNot(HaveOccurred()) diff --git a/controllers/operandconfig/operandconfig_controller.go b/controllers/operandconfig/operandconfig_controller.go index d555b672..52824ac1 100644 --- a/controllers/operandconfig/operandconfig_controller.go +++ b/controllers/operandconfig/operandconfig_controller.go @@ -27,6 +27,7 @@ import ( "github.com/mohae/deepcopy" "github.com/pkg/errors" apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/types" utilerrors "k8s.io/apimachinery/pkg/util/errors" @@ -35,16 +36,17 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/event" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/source" - operatorv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/api/v1alpha1" - "github.com/IBM/operand-deployment-lifecycle-manager/controllers/constant" - deploy "github.com/IBM/operand-deployment-lifecycle-manager/controllers/operator" - "github.com/IBM/operand-deployment-lifecycle-manager/controllers/util" + operatorv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/v4/api/v1alpha1" + "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/constant" + deploy "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/operator" + "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/util" ) // Reconciler reconciles a OperandConfig object @@ -52,6 +54,8 @@ type Reconciler struct { *deploy.ODLMOperator } +//+kubebuilder:rbac:groups=operator.ibm.com,namespace="placeholder",resources=operandconfigs;operandconfigs/status;operandconfigs/finalizers,verbs=get;list;watch;create;update;patch;delete + // Reconcile reads that state of the cluster for a OperandConfig object and makes changes based on the state read // and what is in the OperandConfig.Spec // Note: @@ -128,9 +132,9 @@ func (r *Reconciler) updateStatus(ctx context.Context, instance *operatorv1alpha // Looking for the CSV namespace := r.GetOperatorNamespace(op.InstallMode, op.Namespace) - sub, err := r.GetSubscription(ctx, op.Name, namespace, op.PackageName) + sub, err := r.GetSubscription(ctx, op.Name, namespace, registryInstance.Namespace, op.PackageName) - if apierrors.IsNotFound(err) { + if sub == nil && err == nil { klog.V(3).Infof("There is no Subscription %s or %s in the namespace %s", op.Name, op.PackageName, namespace) continue } @@ -331,7 +335,7 @@ func (r *Reconciler) deleteK8sReousce(ctx context.Context, k8sAPIVersion, k8sKin } else { if r.CheckLabel(k8sUnstruct, map[string]string{constant.OpreqLabel: "true"}) { klog.V(3).Infof("Deleting k8s resource -- Kind: %s, NamespacedName: %s/%s", k8sKind, k8sNamespace, k8sName) - k8sDeleteError := r.Delete(ctx, &k8sUnstruct) + k8sDeleteError := r.Delete(ctx, &k8sUnstruct, client.PropagationPolicy(metav1.DeletePropagationBackground)) if k8sDeleteError != nil && !apierrors.IsNotFound(k8sDeleteError) { return errors.Wrapf(k8sDeleteError, "failed to delete k8s resource -- Kind: %s, NamespacedName: %s/%s", k8sKind, k8sNamespace, k8sName) } @@ -397,7 +401,11 @@ func (r *Reconciler) getRequestToConfigMapper(ctx context.Context) handler.MapFu // SetupWithManager adds OperandConfig controller to the manager. func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error { ctx := context.Background() + options := controller.Options{ + MaxConcurrentReconciles: r.MaxConcurrentReconciles, // Set the desired value for max concurrent reconciles. + } return ctrl.NewControllerManagedBy(mgr). + WithOptions(options). For(&operatorv1alpha1.OperandConfig{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})). Watches(&source.Kind{Type: &operatorv1alpha1.OperandRequest{}}, handler.EnqueueRequestsFromMapFunc(r.getRequestToConfigMapper(ctx)), builder.WithPredicates(predicate.Funcs{ CreateFunc: func(e event.CreateEvent) bool { diff --git a/controllers/operandconfig/operandconfig_controller_test.go b/controllers/operandconfig/operandconfig_controller_test.go index 5aa30e89..168d58c1 100644 --- a/controllers/operandconfig/operandconfig_controller_test.go +++ b/controllers/operandconfig/operandconfig_controller_test.go @@ -24,8 +24,8 @@ import ( olmv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" "k8s.io/apimachinery/pkg/types" - operatorv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/api/v1alpha1" - testutil "github.com/IBM/operand-deployment-lifecycle-manager/controllers/testutil" + operatorv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/v4/api/v1alpha1" + testutil "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/testutil" ) // +kubebuilder:docs-gen:collapse=Imports @@ -106,52 +106,52 @@ var _ = Describe("OperandConfig controller", func() { Expect(k8sClient.Create(ctx, request)).Should(Succeed()) By("Setting status of the Subscriptions") - etcdSub := testutil.Subscription("etcd", operatorNamespaceName) + jaegerSub := testutil.Subscription("jaeger", operatorNamespaceName) Eventually(func() error { - k8sClient.Get(ctx, types.NamespacedName{Name: "etcd", Namespace: operatorNamespaceName}, etcdSub) - etcdSub.Status = testutil.SubscriptionStatus("etcd", operatorNamespaceName, "0.0.1") - return k8sClient.Status().Update(ctx, etcdSub) + k8sClient.Get(ctx, types.NamespacedName{Name: "jaeger", Namespace: operatorNamespaceName}, jaegerSub) + jaegerSub.Status = testutil.SubscriptionStatus("jaeger", operatorNamespaceName, "0.0.1") + return k8sClient.Status().Update(ctx, jaegerSub) }, timeout, interval).Should(Succeed()) - jenkinsSub := testutil.Subscription("jenkins", operatorNamespaceName) + mongodbSub := testutil.Subscription("mongodb-atlas-kubernetes", operatorNamespaceName) Eventually(func() error { - k8sClient.Get(ctx, types.NamespacedName{Name: "jenkins", Namespace: operatorNamespaceName}, jenkinsSub) - jenkinsSub.Status = testutil.SubscriptionStatus("jenkins", operatorNamespaceName, "0.0.1") - return k8sClient.Status().Update(ctx, jenkinsSub) + k8sClient.Get(ctx, types.NamespacedName{Name: "mongodb-atlas-kubernetes", Namespace: operatorNamespaceName}, mongodbSub) + mongodbSub.Status = testutil.SubscriptionStatus("mongodb-atlas-kubernetes", operatorNamespaceName, "0.0.1") + return k8sClient.Status().Update(ctx, mongodbSub) }, timeout, interval).Should(Succeed()) By("Creating and Setting status of the ClusterServiceVersions") - etcdCSV := testutil.ClusterServiceVersion("etcd-csv.v0.0.1", operatorNamespaceName, testutil.EtcdExample) - Expect(k8sClient.Create(ctx, etcdCSV)).Should(Succeed()) + jaegerCSV := testutil.ClusterServiceVersion("jaeger-csv.v0.0.1", "jaeger", operatorNamespaceName, testutil.JaegerExample) + Expect(k8sClient.Create(ctx, jaegerCSV)).Should(Succeed()) Eventually(func() error { - k8sClient.Get(ctx, types.NamespacedName{Name: "etcd-csv.v0.0.1", Namespace: operatorNamespaceName}, etcdCSV) - etcdCSV.Status = testutil.ClusterServiceVersionStatus() - return k8sClient.Status().Update(ctx, etcdCSV) + k8sClient.Get(ctx, types.NamespacedName{Name: "jaeger-csv.v0.0.1", Namespace: operatorNamespaceName}, jaegerCSV) + jaegerCSV.Status = testutil.ClusterServiceVersionStatus() + return k8sClient.Status().Update(ctx, jaegerCSV) }, timeout, interval).Should(Succeed()) - jenkinsCSV := testutil.ClusterServiceVersion("jenkins-csv.v0.0.1", operatorNamespaceName, testutil.JenkinsExample) - Expect(k8sClient.Create(ctx, jenkinsCSV)).Should(Succeed()) + mongodbCSV := testutil.ClusterServiceVersion("mongodb-atlas-kubernetes-csv.v0.0.1", "mongodb-atlas-kubernetes", operatorNamespaceName, testutil.MongodbExample) + Expect(k8sClient.Create(ctx, mongodbCSV)).Should(Succeed()) Eventually(func() error { - k8sClient.Get(ctx, types.NamespacedName{Name: "jenkins-csv.v0.0.1", Namespace: operatorNamespaceName}, jenkinsCSV) - jenkinsCSV.Status = testutil.ClusterServiceVersionStatus() - return k8sClient.Status().Update(ctx, jenkinsCSV) + k8sClient.Get(ctx, types.NamespacedName{Name: "mongodb-atlas-kubernetes-csv.v0.0.1", Namespace: operatorNamespaceName}, mongodbCSV) + mongodbCSV.Status = testutil.ClusterServiceVersionStatus() + return k8sClient.Status().Update(ctx, mongodbCSV) }, timeout, interval).Should(Succeed()) By("Creating and Setting status of the InstallPlan") - etcdIP := testutil.InstallPlan("etcd-install-plan", operatorNamespaceName) - Expect(k8sClient.Create(ctx, etcdIP)).Should(Succeed()) + jaegerIP := testutil.InstallPlan("jaeger-install-plan", operatorNamespaceName) + Expect(k8sClient.Create(ctx, jaegerIP)).Should(Succeed()) Eventually(func() error { - k8sClient.Get(ctx, types.NamespacedName{Name: "etcd-install-plan", Namespace: operatorNamespaceName}, etcdIP) - etcdIP.Status = testutil.InstallPlanStatus() - return k8sClient.Status().Update(ctx, etcdIP) + k8sClient.Get(ctx, types.NamespacedName{Name: "jaeger-install-plan", Namespace: operatorNamespaceName}, jaegerIP) + jaegerIP.Status = testutil.InstallPlanStatus() + return k8sClient.Status().Update(ctx, jaegerIP) }, timeout, interval).Should(Succeed()) - jenkinsIP := testutil.InstallPlan("jenkins-install-plan", operatorNamespaceName) - Expect(k8sClient.Create(ctx, jenkinsIP)).Should(Succeed()) + mongodbIP := testutil.InstallPlan("mongodb-atlas-kubernetes-install-plan", operatorNamespaceName) + Expect(k8sClient.Create(ctx, mongodbIP)).Should(Succeed()) Eventually(func() error { - k8sClient.Get(ctx, types.NamespacedName{Name: "jenkins-install-plan", Namespace: operatorNamespaceName}, jenkinsIP) - jenkinsIP.Status = testutil.InstallPlanStatus() - return k8sClient.Status().Update(ctx, jenkinsIP) + k8sClient.Get(ctx, types.NamespacedName{Name: "mongodb-atlas-kubernetes-install-plan", Namespace: operatorNamespaceName}, mongodbIP) + mongodbIP.Status = testutil.InstallPlanStatus() + return k8sClient.Status().Update(ctx, mongodbIP) }, timeout, interval).Should(Succeed()) By("Checking status of the OperandConfig") @@ -162,12 +162,12 @@ var _ = Describe("OperandConfig controller", func() { }, timeout, interval).Should(Equal(operatorv1alpha1.ServiceRunning)) By("Cleaning up olm resources") - Expect(k8sClient.Delete(ctx, etcdSub)).Should(Succeed()) - Expect(k8sClient.Delete(ctx, jenkinsSub)).Should(Succeed()) - Expect(k8sClient.Delete(ctx, etcdCSV)).Should(Succeed()) - Expect(k8sClient.Delete(ctx, jenkinsCSV)).Should(Succeed()) - Expect(k8sClient.Delete(ctx, etcdIP)).Should(Succeed()) - Expect(k8sClient.Delete(ctx, jenkinsIP)).Should(Succeed()) + Expect(k8sClient.Delete(ctx, jaegerSub)).Should(Succeed()) + Expect(k8sClient.Delete(ctx, mongodbSub)).Should(Succeed()) + Expect(k8sClient.Delete(ctx, jaegerCSV)).Should(Succeed()) + Expect(k8sClient.Delete(ctx, mongodbCSV)).Should(Succeed()) + Expect(k8sClient.Delete(ctx, jaegerIP)).Should(Succeed()) + Expect(k8sClient.Delete(ctx, mongodbIP)).Should(Succeed()) }) }) }) diff --git a/controllers/operandconfig/operandconfig_suite_test.go b/controllers/operandconfig/operandconfig_suite_test.go index 90bafc9d..db18eff8 100644 --- a/controllers/operandconfig/operandconfig_suite_test.go +++ b/controllers/operandconfig/operandconfig_suite_test.go @@ -22,27 +22,27 @@ import ( "testing" "time" - etcdv1beta2 "github.com/coreos/etcd-operator/pkg/apis/etcd/v1beta2" + jaegerv1 "github.com/jaegertracing/jaeger-operator/apis/v1" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/onsi/gomega/gexec" olmv1 "github.com/operator-framework/api/pkg/operators/v1" olmv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" + operatorsv1 "github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/apis/operators/v1" clientgoscheme "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/envtest" - "sigs.k8s.io/controller-runtime/pkg/envtest/printer" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" nssv1 "github.com/IBM/ibm-namespace-scope-operator/api/v1" - apiv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/api/v1alpha1" - "github.com/IBM/operand-deployment-lifecycle-manager/controllers/operandregistry" - "github.com/IBM/operand-deployment-lifecycle-manager/controllers/operandrequest" - deploy "github.com/IBM/operand-deployment-lifecycle-manager/controllers/operator" + apiv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/v4/api/v1alpha1" + "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/operandregistry" + "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/operandrequest" + deploy "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/operator" // +kubebuilder:scaffold:imports ) @@ -64,9 +64,8 @@ var ( func TestOperandConfig(t *testing.T) { RegisterFailHandler(Fail) - RunSpecsWithDefaultAndCustomReporters(t, - "OperandConfig Controller Suite", - []Reporter{printer.NewlineReporter{}}) + RunSpecs(t, + "OperandConfig Controller Suite") } var _ = BeforeSuite(func(done Done) { @@ -93,7 +92,9 @@ var _ = BeforeSuite(func(done Done) { Expect(err).NotTo(HaveOccurred()) err = olmv1.AddToScheme(clientgoscheme.Scheme) Expect(err).NotTo(HaveOccurred()) - err = etcdv1beta2.AddToScheme(clientgoscheme.Scheme) + err = jaegerv1.AddToScheme(clientgoscheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = operatorsv1.AddToScheme(clientgoscheme.Scheme) Expect(err).NotTo(HaveOccurred()) k8sClient, err = client.New(cfg, client.Options{Scheme: clientgoscheme.Scheme}) diff --git a/controllers/operandregistry/operandregistry_controller.go b/controllers/operandregistry/operandregistry_controller.go index 44137df4..28dc895b 100644 --- a/controllers/operandregistry/operandregistry_controller.go +++ b/controllers/operandregistry/operandregistry_controller.go @@ -27,14 +27,15 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/event" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/source" - operatorv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/api/v1alpha1" - deploy "github.com/IBM/operand-deployment-lifecycle-manager/controllers/operator" + operatorv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/v4/api/v1alpha1" + deploy "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/operator" ) // Reconciler reconciles a OperandRegistry object @@ -42,6 +43,8 @@ type Reconciler struct { *deploy.ODLMOperator } +// +kubebuilder:rbac:groups=operator.ibm.com,namespace="placeholder",resources=operandregistries;operandregistries/status;operandregistries/finalizers,verbs=get;list;watch;create;update;patch;delete + // Reconcile reads that state of the cluster for a OperandRegistry object and makes changes based on the state read // and what is in the OperandRegistry.Spec // Note: @@ -75,7 +78,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Re } // Summarize instance status - if instance.Status.OperatorsStatus == nil || len(instance.Status.OperatorsStatus) == 0 { + if len(instance.Status.OperatorsStatus) == 0 { instance.UpdateRegistryPhase(operatorv1alpha1.RegistryReady) } else { instance.UpdateRegistryPhase(operatorv1alpha1.RegistryRunning) @@ -114,7 +117,11 @@ func (r *Reconciler) updateStatus(ctx context.Context, instance *operatorv1alpha // SetupWithManager adds OperandRegistry controller to the manager. func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error { + options := controller.Options{ + MaxConcurrentReconciles: r.MaxConcurrentReconciles, // Set the desired value for max concurrent reconciles. + } return ctrl.NewControllerManagedBy(mgr). + WithOptions(options). For(&operatorv1alpha1.OperandRegistry{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})). Watches(&source.Kind{Type: &operatorv1alpha1.OperandRequest{}}, handler.EnqueueRequestsFromMapFunc(func(a client.Object) []reconcile.Request { or := a.(*operatorv1alpha1.OperandRequest) diff --git a/controllers/operandregistry/operandregistry_controller_test.go b/controllers/operandregistry/operandregistry_controller_test.go index b64d78c8..525f822e 100644 --- a/controllers/operandregistry/operandregistry_controller_test.go +++ b/controllers/operandregistry/operandregistry_controller_test.go @@ -24,8 +24,8 @@ import ( olmv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" "k8s.io/apimachinery/pkg/types" - operatorv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/api/v1alpha1" - "github.com/IBM/operand-deployment-lifecycle-manager/controllers/testutil" + operatorv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/v4/api/v1alpha1" + "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/testutil" ) // +kubebuilder:docs-gen:collapse=Imports @@ -105,52 +105,52 @@ var _ = Describe("OperandRegistry controller", func() { Expect(k8sClient.Create(ctx, request)).Should(Succeed()) By("Setting status of the Subscriptions") - etcdSub := testutil.Subscription("etcd", operatorNamespaceName) + jaegerSub := testutil.Subscription("jaeger", operatorNamespaceName) Eventually(func() error { - k8sClient.Get(ctx, types.NamespacedName{Name: "etcd", Namespace: operatorNamespaceName}, etcdSub) - etcdSub.Status = testutil.SubscriptionStatus("etcd", operatorNamespaceName, "0.0.1") - return k8sClient.Status().Update(ctx, etcdSub) + k8sClient.Get(ctx, types.NamespacedName{Name: "jaeger", Namespace: operatorNamespaceName}, jaegerSub) + jaegerSub.Status = testutil.SubscriptionStatus("jaeger", operatorNamespaceName, "0.0.1") + return k8sClient.Status().Update(ctx, jaegerSub) }, timeout, interval).Should(Succeed()) - jenkinsSub := testutil.Subscription("jenkins", operatorNamespaceName) + mongodbSub := testutil.Subscription("mongodb-atlas-kubernetes", operatorNamespaceName) Eventually(func() error { - k8sClient.Get(ctx, types.NamespacedName{Name: "jenkins", Namespace: operatorNamespaceName}, jenkinsSub) - jenkinsSub.Status = testutil.SubscriptionStatus("jenkins", operatorNamespaceName, "0.0.1") - return k8sClient.Status().Update(ctx, jenkinsSub) + k8sClient.Get(ctx, types.NamespacedName{Name: "mongodb-atlas-kubernetes", Namespace: operatorNamespaceName}, mongodbSub) + mongodbSub.Status = testutil.SubscriptionStatus("mongodb-atlas-kubernetes", operatorNamespaceName, "0.0.1") + return k8sClient.Status().Update(ctx, mongodbSub) }, timeout, interval).Should(Succeed()) By("Creating and Setting status of the ClusterServiceVersions") - etcdCSV := testutil.ClusterServiceVersion("etcd-csv.v0.0.1", operatorNamespaceName, testutil.EtcdExample) - Expect(k8sClient.Create(ctx, etcdCSV)).Should(Succeed()) + jaegerCSV := testutil.ClusterServiceVersion("jaeger-csv.v0.0.1", "jaeger", operatorNamespaceName, testutil.JaegerExample) + Expect(k8sClient.Create(ctx, jaegerCSV)).Should(Succeed()) Eventually(func() error { - k8sClient.Get(ctx, types.NamespacedName{Name: "etcd-csv.v0.0.1", Namespace: operatorNamespaceName}, etcdCSV) - etcdCSV.Status = testutil.ClusterServiceVersionStatus() - return k8sClient.Status().Update(ctx, etcdCSV) + k8sClient.Get(ctx, types.NamespacedName{Name: "jaeger-csv.v0.0.1", Namespace: operatorNamespaceName}, jaegerCSV) + jaegerCSV.Status = testutil.ClusterServiceVersionStatus() + return k8sClient.Status().Update(ctx, jaegerCSV) }, timeout, interval).Should(Succeed()) - jenkinsCSV := testutil.ClusterServiceVersion("jenkins-csv.v0.0.1", operatorNamespaceName, testutil.JenkinsExample) - Expect(k8sClient.Create(ctx, jenkinsCSV)).Should(Succeed()) + mongodbCSV := testutil.ClusterServiceVersion("mongodb-atlas-kubernetes-csv.v0.0.1", "mongodb-atlas-kubernetes", operatorNamespaceName, testutil.MongodbExample) + Expect(k8sClient.Create(ctx, mongodbCSV)).Should(Succeed()) Eventually(func() error { - k8sClient.Get(ctx, types.NamespacedName{Name: "jenkins-csv.v0.0.1", Namespace: operatorNamespaceName}, jenkinsCSV) - jenkinsCSV.Status = testutil.ClusterServiceVersionStatus() - return k8sClient.Status().Update(ctx, jenkinsCSV) + k8sClient.Get(ctx, types.NamespacedName{Name: "mongodb-atlas-kubernetes-csv.v0.0.1", Namespace: operatorNamespaceName}, mongodbCSV) + mongodbCSV.Status = testutil.ClusterServiceVersionStatus() + return k8sClient.Status().Update(ctx, mongodbCSV) }, timeout, interval).Should(Succeed()) By("Creating and Setting status of the InstallPlan") - etcdIP := testutil.InstallPlan("etcd-install-plan", operatorNamespaceName) - Expect(k8sClient.Create(ctx, etcdIP)).Should(Succeed()) + jaegerIP := testutil.InstallPlan("jaeger-install-plan", operatorNamespaceName) + Expect(k8sClient.Create(ctx, jaegerIP)).Should(Succeed()) Eventually(func() error { - k8sClient.Get(ctx, types.NamespacedName{Name: "etcd-install-plan", Namespace: operatorNamespaceName}, etcdIP) - etcdIP.Status = testutil.InstallPlanStatus() - return k8sClient.Status().Update(ctx, etcdIP) + k8sClient.Get(ctx, types.NamespacedName{Name: "jaeger-install-plan", Namespace: operatorNamespaceName}, jaegerIP) + jaegerIP.Status = testutil.InstallPlanStatus() + return k8sClient.Status().Update(ctx, jaegerIP) }, timeout, interval).Should(Succeed()) - jenkinsIP := testutil.InstallPlan("jenkins-install-plan", operatorNamespaceName) - Expect(k8sClient.Create(ctx, jenkinsIP)).Should(Succeed()) + mongodbIP := testutil.InstallPlan("mongodb-atlas-kubernetes-install-plan", operatorNamespaceName) + Expect(k8sClient.Create(ctx, mongodbIP)).Should(Succeed()) Eventually(func() error { - k8sClient.Get(ctx, types.NamespacedName{Name: "jenkins-install-plan", Namespace: operatorNamespaceName}, jenkinsIP) - jenkinsIP.Status = testutil.InstallPlanStatus() - return k8sClient.Status().Update(ctx, jenkinsIP) + k8sClient.Get(ctx, types.NamespacedName{Name: "mongodb-atlas-kubernetes-install-plan", Namespace: operatorNamespaceName}, mongodbIP) + mongodbIP.Status = testutil.InstallPlanStatus() + return k8sClient.Status().Update(ctx, mongodbIP) }, timeout, interval).Should(Succeed()) By("Checking status of the OperandRegistry") @@ -161,12 +161,12 @@ var _ = Describe("OperandRegistry controller", func() { }, timeout, interval).Should(Equal(operatorv1alpha1.RegistryRunning)) By("Cleaning up olm resources") - Expect(k8sClient.Delete(ctx, etcdSub)).Should(Succeed()) - Expect(k8sClient.Delete(ctx, jenkinsSub)).Should(Succeed()) - Expect(k8sClient.Delete(ctx, etcdCSV)).Should(Succeed()) - Expect(k8sClient.Delete(ctx, jenkinsCSV)).Should(Succeed()) - Expect(k8sClient.Delete(ctx, etcdIP)).Should(Succeed()) - Expect(k8sClient.Delete(ctx, jenkinsIP)).Should(Succeed()) + Expect(k8sClient.Delete(ctx, jaegerSub)).Should(Succeed()) + Expect(k8sClient.Delete(ctx, mongodbSub)).Should(Succeed()) + Expect(k8sClient.Delete(ctx, jaegerCSV)).Should(Succeed()) + Expect(k8sClient.Delete(ctx, mongodbCSV)).Should(Succeed()) + Expect(k8sClient.Delete(ctx, jaegerIP)).Should(Succeed()) + Expect(k8sClient.Delete(ctx, mongodbIP)).Should(Succeed()) }) }) }) diff --git a/controllers/operandregistry/operandregistry_suite_test.go b/controllers/operandregistry/operandregistry_suite_test.go index b73c1845..041b2e6b 100644 --- a/controllers/operandregistry/operandregistry_suite_test.go +++ b/controllers/operandregistry/operandregistry_suite_test.go @@ -22,26 +22,26 @@ import ( "testing" "time" - etcdv1beta2 "github.com/coreos/etcd-operator/pkg/apis/etcd/v1beta2" + jaegerv1 "github.com/jaegertracing/jaeger-operator/apis/v1" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/onsi/gomega/gexec" olmv1 "github.com/operator-framework/api/pkg/operators/v1" olmv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" + operatorsv1 "github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/apis/operators/v1" clientgoscheme "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/envtest" - "sigs.k8s.io/controller-runtime/pkg/envtest/printer" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" nssv1 "github.com/IBM/ibm-namespace-scope-operator/api/v1" - apiv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/api/v1alpha1" - "github.com/IBM/operand-deployment-lifecycle-manager/controllers/operandconfig" - "github.com/IBM/operand-deployment-lifecycle-manager/controllers/operandrequest" - deploy "github.com/IBM/operand-deployment-lifecycle-manager/controllers/operator" + apiv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/v4/api/v1alpha1" + "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/operandconfig" + "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/operandrequest" + deploy "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/operator" // +kubebuilder:scaffold:imports ) @@ -63,9 +63,8 @@ var ( func TestOperandRegistry(t *testing.T) { RegisterFailHandler(Fail) - RunSpecsWithDefaultAndCustomReporters(t, - "OperandRegistry Controller Suite", - []Reporter{printer.NewlineReporter{}}) + RunSpecs(t, + "OperandRegistry Controller Suite") } var _ = BeforeSuite(func(done Done) { @@ -92,7 +91,9 @@ var _ = BeforeSuite(func(done Done) { Expect(err).NotTo(HaveOccurred()) err = olmv1.AddToScheme(clientgoscheme.Scheme) Expect(err).NotTo(HaveOccurred()) - err = etcdv1beta2.AddToScheme(clientgoscheme.Scheme) + err = jaegerv1.AddToScheme(clientgoscheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = operatorsv1.AddToScheme(clientgoscheme.Scheme) Expect(err).NotTo(HaveOccurred()) k8sClient, err = client.New(cfg, client.Options{Scheme: clientgoscheme.Scheme}) diff --git a/controllers/operandrequest/operandrequest_controller.go b/controllers/operandrequest/operandrequest_controller.go index 88da6493..2804d1ca 100644 --- a/controllers/operandrequest/operandrequest_controller.go +++ b/controllers/operandrequest/operandrequest_controller.go @@ -31,20 +31,22 @@ import ( "github.com/pkg/errors" authorizationv1 "k8s.io/api/authorization/v1" corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/klog" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/event" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/source" - operatorv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/api/v1alpha1" - "github.com/IBM/operand-deployment-lifecycle-manager/controllers/constant" - deploy "github.com/IBM/operand-deployment-lifecycle-manager/controllers/operator" + operatorv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/v4/api/v1alpha1" + "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/constant" + deploy "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/operator" ) // Reconciler reconciles a OperandRequest object @@ -59,6 +61,19 @@ type clusterObjects struct { subscription *olmv1alpha1.Subscription } +//+kubebuilder:rbac:groups=operator.ibm.com,resources=certmanagers;auditloggings,verbs=get;delete +//+kubebuilder:rbac:groups=operators.coreos.com,resources=catalogsources,verbs=get +//+kubebuilder:rbac:groups=apiextensions.k8s.io,resources=customresourcedefinitions,verbs=get + +//+kubebuilder:rbac:groups=*,namespace="placeholder",resources=*,verbs=create;delete;get;list;patch;update;watch +//+kubebuilder:rbac:groups=operator.ibm.com,namespace="placeholder",resources=operandrequests;operandrequests/status;operandrequests/finalizers,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups="",namespace="placeholder",resources=configmaps;secrets;services;namespaces,verbs=create;delete;get;list;patch;update;watch +//+kubebuilder:rbac:groups=route.openshift.io,namespace="placeholder",resources=routes,verbs=create;delete;get;list;patch;update;watch +//+kubebuilder:rbac:groups=operators.coreos.com,namespace="placeholder",resources=operatorgroups;installplans,verbs=create;delete;get;list;patch;update;watch +//+kubebuilder:rbac:groups=k8s.keycloak.org,namespace="placeholder",resources=keycloaks;keycloakrealmimports,verbs=create;delete;get;list;patch;update;watch +//+kubebuilder:rbac:groups=packages.operators.coreos.com,namespace="placeholder",resources=packagemanifests,verbs=get;list;patch;update;watch +//+kubebuilder:rbac:groups=operators.coreos.com,namespace="placeholder",resources=clusterserviceversions;subscriptions,verbs=create;delete;get;list;patch;update;watch + // Reconcile reads that state of the cluster for a OperandRequest object and makes changes based on the state read // and what is in the OperandRequest.Spec // Note: @@ -76,12 +91,25 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Re // Always attempt to patch the status after each reconciliation. defer func() { - if reflect.DeepEqual(originalInstance.Status, requestInstance.Status) { + // get the latest instance from the server and check if the status has changed + existingInstance := &operatorv1alpha1.OperandRequest{} + if err := r.Client.Get(ctx, req.NamespacedName, existingInstance); err != nil && !apierrors.IsNotFound(err) { + // Error reading the latest object - requeue the request. + reconcileErr = utilerrors.NewAggregate([]error{reconcileErr, fmt.Errorf("error while get latest OperandRequest.Status from server: %v", err)}) + } + + if reflect.DeepEqual(existingInstance.Status, requestInstance.Status) { return } - if err := r.Client.Status().Patch(ctx, requestInstance, client.MergeFrom(originalInstance)); err != nil { + + // Update requestInstance's resource version to avoid conflicts + requestInstance.ResourceVersion = existingInstance.ResourceVersion + if err := r.Client.Status().Patch(ctx, requestInstance, client.MergeFrom(existingInstance)); err != nil && !apierrors.IsNotFound(err) { reconcileErr = utilerrors.NewAggregate([]error{reconcileErr, fmt.Errorf("error while patching OperandRequest.Status: %v", err)}) } + if reconcileErr != nil { + klog.Errorf("failed to patch status for OperandRequest %s: %v", req.NamespacedName.String(), reconcileErr) + } }() // Remove finalizer when DeletionTimestamp none zero @@ -123,7 +151,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Re } // Initialize the status for OperandRequest instance - if !requestInstance.InitRequestStatus() { + if !requestInstance.InitRequestStatus(&r.Mutex) { return ctrl.Result{Requeue: true}, nil } @@ -153,8 +181,13 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Re return ctrl.Result{RequeueAfter: constant.DefaultRequeueDuration}, nil } + //check if status.services is present (if a relevant service was requested), requeue again if service is not ready yet + if isReady, err := r.ServiceStatusIsReady(ctx, requestInstance); !isReady || err != nil { + return ctrl.Result{RequeueAfter: constant.DefaultRequeueDuration}, nil + } + klog.V(1).Infof("Finished reconciling OperandRequest: %s", req.NamespacedName) - return ctrl.Result{RequeueAfter: constant.DefaultSyncPeriod}, nil + return ctrl.Result{RequeueAfter: constant.DefaultSyncPeriod}, reconcileErr } func (r *Reconciler) checkPermission(ctx context.Context, req ctrl.Request) bool { @@ -208,21 +241,25 @@ func (r *Reconciler) addFinalizer(ctx context.Context, cr *operatorv1alpha1.Oper func (r *Reconciler) checkFinalizer(ctx context.Context, requestInstance *operatorv1alpha1.OperandRequest) error { klog.V(1).Infof("Deleting OperandRequest %s in the namespace %s", requestInstance.Name, requestInstance.Namespace) - failedDeletedOperands := gset.NewSet() - existingSub := &olmv1alpha1.SubscriptionList{} - - opts := []client.ListOption{ - client.MatchingLabels(map[string]string{constant.OpreqLabel: "true"}), - } - - if err := r.Client.List(ctx, existingSub, opts...); err != nil { - return err - } - if len(existingSub.Items) == 0 { - return nil + remainingOperands := gset.NewSet() + for _, m := range requestInstance.Status.Members { + remainingOperands.Add(m.Name) } + // TODO: update to check OperandRequest status to see if member is user managed or not + // existingSub := &olmv1alpha1.SubscriptionList{} + + // opts := []client.ListOption{ + // client.MatchingLabels(map[string]string{constant.OpreqLabel: "true"}), + // } + + // if err := r.Client.List(ctx, existingSub, opts...); err != nil { + // return err + // } + // if len(existingSub.Items) == 0 { + // return nil + // } // Delete all the subscriptions that created by current request - if err := r.absentOperatorsAndOperands(ctx, requestInstance, &failedDeletedOperands); err != nil { + if err := r.absentOperatorsAndOperands(ctx, requestInstance, &remainingOperands); err != nil { return err } return nil @@ -245,14 +282,14 @@ func (r *Reconciler) getRegistryToRequestMapper() handler.MapFunc { func (r *Reconciler) getSubToRequestMapper() handler.MapFunc { return func(object client.Object) []ctrl.Request { - reg, _ := regexp.Compile(`^(.*)\.(.*)\/request`) + reg, _ := regexp.Compile(`^(.*)\.(.*)\.(.*)\/request`) annotations := object.GetAnnotations() var reqName, reqNamespace string for annotation := range annotations { if reg.MatchString(annotation) { annotationSlices := strings.Split(annotation, ".") reqNamespace = annotationSlices[0] - reqName = strings.Split(annotationSlices[1], "/")[0] + reqName = annotationSlices[1] } } if reqNamespace == "" || reqName == "" { @@ -282,16 +319,93 @@ func (r *Reconciler) getConfigToRequestMapper() handler.MapFunc { } } +func (r *Reconciler) getReferenceToRequestMapper() handler.MapFunc { + ctx := context.Background() + return func(object client.Object) []ctrl.Request { + annotations := object.GetAnnotations() + if annotations == nil { + return []ctrl.Request{} + } + odlmReference, ok := annotations[constant.ODLMReferenceAnnotation] + if !ok { + return []ctrl.Request{} + } + odlmReferenceSlices := strings.Split(odlmReference, ".") + if len(odlmReferenceSlices) != 3 { + return []ctrl.Request{} + } + + var requestList []operatorv1alpha1.OperandRequest + if odlmReferenceSlices[0] == "OperandConfig" { + requestList, _ = r.ListOperandRequestsByConfig(ctx, types.NamespacedName{Namespace: odlmReferenceSlices[1], Name: odlmReferenceSlices[2]}) + } else if odlmReferenceSlices[0] == "OperandRegistry" { + requestList, _ = r.ListOperandRequestsByRegistry(ctx, types.NamespacedName{Namespace: odlmReferenceSlices[1], Name: odlmReferenceSlices[2]}) + } else { + return []ctrl.Request{} + } + + requests := []ctrl.Request{} + for _, request := range requestList { + namespaceName := types.NamespacedName{Name: request.Name, Namespace: request.Namespace} + req := ctrl.Request{NamespacedName: namespaceName} + requests = append(requests, req) + } + return requests + } +} + // SetupWithManager adds OperandRequest controller to the manager. func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error { + options := controller.Options{ + MaxConcurrentReconciles: r.MaxConcurrentReconciles, // Set the desired value for max concurrent reconciles. + } + ReferencePredicates := predicate.Funcs{ + CreateFunc: func(e event.CreateEvent) bool { + labels := e.Object.GetLabels() + // only return true when both conditions are met at the same time: + // 1. label contain key "constant.ODLMWatchedLabel" and value is true + // 2. label does not contain key "constant.OpbiTypeLabel" with value "copy" + if labels != nil { + if labelValue, ok := labels[constant.ODLMWatchedLabel]; ok && labelValue == "true" { + if labelValue, ok := labels[constant.OpbiTypeLabel]; ok && labelValue == "copy" { + return false + } + return true + } + } + return false + }, + UpdateFunc: func(e event.UpdateEvent) bool { + // If the object is not updated except the ODLMWatchedLabel label or ODLMReferenceAnnotation annotation, return false + if !r.ObjectIsUpdatedWithException(&e.ObjectOld, &e.ObjectNew) { + return false + } + labels := e.ObjectNew.GetLabels() + if labels != nil { + if labelValue, ok := labels[constant.ODLMWatchedLabel]; ok && labelValue == "true" { + if labelValue, ok := labels[constant.OpbiTypeLabel]; ok && labelValue == "copy" { + return false + } + return true + } + } + return false + }, + DeleteFunc: func(e event.DeleteEvent) bool { + return true + }, + } return ctrl.NewControllerManagedBy(mgr). + WithOptions(options). For(&operatorv1alpha1.OperandRequest{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})). Watches(&source.Kind{Type: &olmv1alpha1.Subscription{}}, handler.EnqueueRequestsFromMapFunc(r.getSubToRequestMapper()), builder.WithPredicates(predicate.Funcs{ UpdateFunc: func(e event.UpdateEvent) bool { oldObject := e.ObjectOld.(*olmv1alpha1.Subscription) newObject := e.ObjectNew.(*olmv1alpha1.Subscription) if oldObject.Labels != nil && oldObject.Labels[constant.OpreqLabel] == "true" { - return (oldObject.Status.InstalledCSV != "" && newObject.Status.InstalledCSV != "" && oldObject.Status.InstalledCSV != newObject.Status.InstalledCSV) + statusToggle := (oldObject.Status.InstalledCSV != "" && newObject.Status.InstalledCSV != "" && oldObject.Status.InstalledCSV != newObject.Status.InstalledCSV) + metadataToggle := !reflect.DeepEqual(oldObject.Annotations, newObject.Annotations) + return statusToggle || metadataToggle } return false }, @@ -304,7 +418,7 @@ func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error { UpdateFunc: func(e event.UpdateEvent) bool { oldObject := e.ObjectOld.(*operatorv1alpha1.OperandRegistry) newObject := e.ObjectNew.(*operatorv1alpha1.OperandRegistry) - return !reflect.DeepEqual(oldObject.Spec, newObject.Spec) + return !reflect.DeepEqual(oldObject.Spec, newObject.Spec) || !reflect.DeepEqual(oldObject.GetAnnotations(), newObject.GetAnnotations()) }, DeleteFunc: func(e event.DeleteEvent) bool { // Evaluates to false if the object has been confirmed deleted. @@ -321,5 +435,8 @@ func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error { newObject := e.ObjectNew.(*operatorv1alpha1.OperandConfig) return !reflect.DeepEqual(oldObject.Spec, newObject.Spec) }, - })).Complete(r) + })). + Watches(&source.Kind{Type: &corev1.ConfigMap{}}, handler.EnqueueRequestsFromMapFunc(r.getReferenceToRequestMapper()), builder.WithPredicates(ReferencePredicates)). + Watches(&source.Kind{Type: &corev1.Secret{}}, handler.EnqueueRequestsFromMapFunc(r.getReferenceToRequestMapper()), builder.WithPredicates(ReferencePredicates)). + Complete(r) } diff --git a/controllers/operandrequest/operandrequest_controller_test.go b/controllers/operandrequest/operandrequest_controller_test.go index c1aa2b28..71c15273 100644 --- a/controllers/operandrequest/operandrequest_controller_test.go +++ b/controllers/operandrequest/operandrequest_controller_test.go @@ -20,9 +20,9 @@ import ( "context" "crypto/sha256" "encoding/hex" - "reflect" - v1beta2 "github.com/coreos/etcd-operator/pkg/apis/etcd/v1beta2" + "github.com/google/go-cmp/cmp" + jaegerv1 "github.com/jaegertracing/jaeger-operator/apis/v1" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" olmv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" @@ -30,20 +30,20 @@ import ( "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" - operatorv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/api/v1alpha1" - "github.com/IBM/operand-deployment-lifecycle-manager/controllers/testutil" + operatorv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/v4/api/v1alpha1" + "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/testutil" ) // +kubebuilder:docs-gen:collapse=Imports -var _ = Describe("OperandRegistry controller", func() { +var _ = Describe("OperandRequest controller", func() { const ( name1 = "ibm-cloudpak-name" name2 = "ibm-cloudpack-name-2" namespace = "ibm-cloudpak" registryName1 = "common-service" registryName2 = "common-service-2" - registryNamespace = "ibm-common-services" + registryNamespace = "data-ns" operatorNamespace = "ibm-operators" ) @@ -126,102 +126,102 @@ var _ = Describe("OperandRegistry controller", func() { By("Setting status of the Subscriptions") Eventually(func() error { - etcdSub := &olmv1alpha1.Subscription{} - Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "etcd", Namespace: operatorNamespaceName}, etcdSub)).Should(Succeed()) - etcdSub.Status = testutil.SubscriptionStatus("etcd", operatorNamespaceName, "0.0.1") - return k8sClient.Status().Update(ctx, etcdSub) + jaegerSub := &olmv1alpha1.Subscription{} + Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "jaeger", Namespace: operatorNamespaceName}, jaegerSub)).Should(Succeed()) + jaegerSub.Status = testutil.SubscriptionStatus("jaeger", operatorNamespaceName, "0.0.1") + return k8sClient.Status().Update(ctx, jaegerSub) }, testutil.Timeout, testutil.Interval).Should(Succeed()) Eventually(func() error { - jenkinsSub := &olmv1alpha1.Subscription{} - Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "jenkins", Namespace: operatorNamespaceName}, jenkinsSub)).Should(Succeed()) - jenkinsSub.Status = testutil.SubscriptionStatus("jenkins", operatorNamespaceName, "0.0.1") - return k8sClient.Status().Update(ctx, jenkinsSub) + mongodbSub := &olmv1alpha1.Subscription{} + Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "mongodb-atlas-kubernetes", Namespace: operatorNamespaceName}, mongodbSub)).Should(Succeed()) + mongodbSub.Status = testutil.SubscriptionStatus("mongodb-atlas-kubernetes", operatorNamespaceName, "0.0.1") + return k8sClient.Status().Update(ctx, mongodbSub) }, testutil.Timeout, testutil.Interval).Should(Succeed()) By("Creating and Setting status of the ClusterServiceVersions") - etcdCSV := testutil.ClusterServiceVersion("etcd-csv.v0.0.1", operatorNamespaceName, testutil.EtcdExample) - Expect(k8sClient.Create(ctx, etcdCSV)).Should(Succeed()) + jaegerCSV := testutil.ClusterServiceVersion("jaeger-csv.v0.0.1", "jaeger", operatorNamespaceName, testutil.JaegerExample) + Expect(k8sClient.Create(ctx, jaegerCSV)).Should(Succeed()) Eventually(func() error { - k8sClient.Get(ctx, types.NamespacedName{Name: "etcd-csv.v0.0.1", Namespace: operatorNamespaceName}, etcdCSV) - etcdCSV.Status = testutil.ClusterServiceVersionStatus() - return k8sClient.Status().Update(ctx, etcdCSV) + k8sClient.Get(ctx, types.NamespacedName{Name: "jaeger-csv.v0.0.1", Namespace: operatorNamespaceName}, jaegerCSV) + jaegerCSV.Status = testutil.ClusterServiceVersionStatus() + return k8sClient.Status().Update(ctx, jaegerCSV) }, testutil.Timeout, testutil.Interval).Should(Succeed()) - jenkinsCSV := testutil.ClusterServiceVersion("jenkins-csv.v0.0.1", operatorNamespaceName, testutil.JenkinsExample) - Expect(k8sClient.Create(ctx, jenkinsCSV)).Should(Succeed()) + mongodbCSV := testutil.ClusterServiceVersion("mongodb-atlas-kubernetes-csv.v0.0.1", "mongodb-atlas-kubernetes", operatorNamespaceName, testutil.MongodbExample) + Expect(k8sClient.Create(ctx, mongodbCSV)).Should(Succeed()) Eventually(func() error { - k8sClient.Get(ctx, types.NamespacedName{Name: "jenkins-csv.v0.0.1", Namespace: operatorNamespaceName}, jenkinsCSV) - jenkinsCSV.Status = testutil.ClusterServiceVersionStatus() - return k8sClient.Status().Update(ctx, jenkinsCSV) + k8sClient.Get(ctx, types.NamespacedName{Name: "mongodb-atlas-kubernetes-csv.v0.0.1", Namespace: operatorNamespaceName}, mongodbCSV) + mongodbCSV.Status = testutil.ClusterServiceVersionStatus() + return k8sClient.Status().Update(ctx, mongodbCSV) }, testutil.Timeout, testutil.Interval).Should(Succeed()) By("Creating and Setting status of the InstallPlan") - etcdIP := testutil.InstallPlan("etcd-install-plan", operatorNamespaceName) - Expect(k8sClient.Create(ctx, etcdIP)).Should(Succeed()) + jaegerIP := testutil.InstallPlan("jaeger-install-plan", operatorNamespaceName) + Expect(k8sClient.Create(ctx, jaegerIP)).Should(Succeed()) Eventually(func() error { - k8sClient.Get(ctx, types.NamespacedName{Name: "etcd-install-plan", Namespace: operatorNamespaceName}, etcdIP) - etcdIP.Status = testutil.InstallPlanStatus() - return k8sClient.Status().Update(ctx, etcdIP) + k8sClient.Get(ctx, types.NamespacedName{Name: "jaeger-install-plan", Namespace: operatorNamespaceName}, jaegerIP) + jaegerIP.Status = testutil.InstallPlanStatus() + return k8sClient.Status().Update(ctx, jaegerIP) }, testutil.Timeout, testutil.Interval).Should(Succeed()) - jenkinsIP := testutil.InstallPlan("jenkins-install-plan", operatorNamespaceName) - Expect(k8sClient.Create(ctx, jenkinsIP)).Should(Succeed()) + mongodbIP := testutil.InstallPlan("mongodb-atlas-kubernetes-install-plan", operatorNamespaceName) + Expect(k8sClient.Create(ctx, mongodbIP)).Should(Succeed()) Eventually(func() error { - k8sClient.Get(ctx, types.NamespacedName{Name: "jenkins-install-plan", Namespace: operatorNamespaceName}, jenkinsIP) - jenkinsIP.Status = testutil.InstallPlanStatus() - return k8sClient.Status().Update(ctx, jenkinsIP) + k8sClient.Get(ctx, types.NamespacedName{Name: "mongodb-atlas-kubernetes-install-plan", Namespace: operatorNamespaceName}, mongodbIP) + mongodbIP.Status = testutil.InstallPlanStatus() + return k8sClient.Status().Update(ctx, mongodbIP) }, testutil.Timeout, testutil.Interval).Should(Succeed()) - By("Checking first CR of the etcd operator") + By("Checking first CR of the jaeger operator") Eventually(func() error { - etcdCluster := &v1beta2.EtcdCluster{} - err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: "example", Namespace: namespaceName}, etcdCluster) + jaegerCR := &jaegerv1.Jaeger{} + err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: "my-jaeger", Namespace: namespaceName}, jaegerCR) return err }, testutil.Timeout, testutil.Interval).Should(Succeed()) - By("Checking second CR of the etcd operator") + By("Checking second CR of the jaeger operator") Eventually(func() error { - etcdCluster := &v1beta2.EtcdCluster{} - crInfo := sha256.Sum256([]byte("etcd.database.coreos.com/v1beta2" + "EtcdCluster" + "1")) - etcdCRName := name1 + "-" + hex.EncodeToString(crInfo[:7]) - err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: etcdCRName, Namespace: namespaceName}, etcdCluster) + jaegerCR := &jaegerv1.Jaeger{} + crInfo := sha256.Sum256([]byte("jaegertracing.io/v1" + "Jaeger" + "1")) + jaegerCRName := name1 + "-" + hex.EncodeToString(crInfo[:7]) + err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: jaegerCRName, Namespace: namespaceName}, jaegerCR) return err }, testutil.Timeout, testutil.Interval).Should(Succeed()) By("Deleting the OperandRequest") Expect(k8sClient.Delete(ctx, requestWithCR)).Should(Succeed()) - By("Checking CR of the etcd operator has been deleted") + By("Checking CR of the jaeger operator has been deleted") Eventually(func() bool { - etcdCluster := &v1beta2.EtcdCluster{} - err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: "example", Namespace: namespaceName}, etcdCluster) + jaegerCR := &jaegerv1.Jaeger{} + err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: "my-jaeger", Namespace: namespaceName}, jaegerCR) return err != nil && errors.IsNotFound(err) }, testutil.Timeout, testutil.Interval).Should(BeTrue()) By("Checking operators have been deleted") Eventually(func() bool { - jenkinsSub := &olmv1alpha1.Subscription{} - err := k8sClient.Get(ctx, types.NamespacedName{Name: "jenkins", Namespace: operatorNamespaceName}, jenkinsSub) + mongodbSub := &olmv1alpha1.Subscription{} + err := k8sClient.Get(ctx, types.NamespacedName{Name: "mongodb-atlas-kubernetes", Namespace: operatorNamespaceName}, mongodbSub) return err != nil && errors.IsNotFound(err) }, testutil.Timeout, testutil.Interval).Should(BeTrue()) Eventually(func() bool { - jenkinsCSV := &olmv1alpha1.ClusterServiceVersion{} - err := k8sClient.Get(ctx, types.NamespacedName{Name: "jenkins-csv.v0.0.1", Namespace: operatorNamespaceName}, jenkinsCSV) + mongodbCSV := &olmv1alpha1.ClusterServiceVersion{} + err := k8sClient.Get(ctx, types.NamespacedName{Name: "mongodb-atlas-kubernetes-csv.v0.0.1", Namespace: operatorNamespaceName}, mongodbCSV) return err != nil && errors.IsNotFound(err) }, testutil.Timeout, testutil.Interval).Should(BeTrue()) Eventually(func() bool { - etcdSub := &olmv1alpha1.Subscription{} - err := k8sClient.Get(ctx, types.NamespacedName{Name: "etcd", Namespace: operatorNamespaceName}, etcdSub) + jaegerSub := &olmv1alpha1.Subscription{} + err := k8sClient.Get(ctx, types.NamespacedName{Name: "jaeger", Namespace: operatorNamespaceName}, jaegerSub) return err != nil && errors.IsNotFound(err) }, testutil.Timeout, testutil.Interval).Should(BeTrue()) Eventually(func() bool { - etcdCSV := &olmv1alpha1.ClusterServiceVersion{} - err := k8sClient.Get(ctx, types.NamespacedName{Name: "etcd-csv.v0.0.1", Namespace: operatorNamespaceName}, etcdCSV) + jaegerCSV := &olmv1alpha1.ClusterServiceVersion{} + err := k8sClient.Get(ctx, types.NamespacedName{Name: "jaeger-csv.v0.0.1", Namespace: operatorNamespaceName}, jaegerCSV) return err != nil && errors.IsNotFound(err) }, testutil.Timeout, testutil.Interval).Should(BeTrue()) @@ -258,68 +258,81 @@ var _ = Describe("OperandRegistry controller", func() { By("Setting status of the Subscriptions") Eventually(func() error { - etcdSub := &olmv1alpha1.Subscription{} - Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "etcd", Namespace: operatorNamespaceName}, etcdSub)).Should(Succeed()) - etcdSub.Status = testutil.SubscriptionStatus("etcd", operatorNamespaceName, "0.0.1") - return k8sClient.Status().Update(ctx, etcdSub) + jaegerSub := &olmv1alpha1.Subscription{} + Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "jaeger", Namespace: operatorNamespaceName}, jaegerSub)).Should(Succeed()) + jaegerSub.Status = testutil.SubscriptionStatus("jaeger", operatorNamespaceName, "0.0.1") + return k8sClient.Status().Update(ctx, jaegerSub) }, testutil.Timeout, testutil.Interval).Should(Succeed()) Eventually(func() error { - jenkinsSub := &olmv1alpha1.Subscription{} - Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "jenkins", Namespace: operatorNamespaceName}, jenkinsSub)).Should(Succeed()) - jenkinsSub.Status = testutil.SubscriptionStatus("jenkins", operatorNamespaceName, "0.0.1") - return k8sClient.Status().Update(ctx, jenkinsSub) + mongodbSub := &olmv1alpha1.Subscription{} + Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "mongodb-atlas-kubernetes", Namespace: operatorNamespaceName}, mongodbSub)).Should(Succeed()) + mongodbSub.Status = testutil.SubscriptionStatus("mongodb-atlas-kubernetes", operatorNamespaceName, "0.0.1") + return k8sClient.Status().Update(ctx, mongodbSub) }, testutil.Timeout, testutil.Interval).Should(Succeed()) By("Creating and Setting status of the ClusterServiceVersions") - etcdCSV := testutil.ClusterServiceVersion("etcd-csv.v0.0.1", operatorNamespaceName, testutil.EtcdExample) - Expect(k8sClient.Create(ctx, etcdCSV)).Should(Succeed()) + jaegerCSV := testutil.ClusterServiceVersion("jaeger-csv.v0.0.1", "jaeger", operatorNamespaceName, testutil.JaegerExample) + Expect(k8sClient.Create(ctx, jaegerCSV)).Should(Succeed()) Eventually(func() error { - k8sClient.Get(ctx, types.NamespacedName{Name: "etcd-csv.v0.0.1", Namespace: operatorNamespaceName}, etcdCSV) - etcdCSV.Status = testutil.ClusterServiceVersionStatus() - return k8sClient.Status().Update(ctx, etcdCSV) + k8sClient.Get(ctx, types.NamespacedName{Name: "jaeger-csv.v0.0.1", Namespace: operatorNamespaceName}, jaegerCSV) + jaegerCSV.Status = testutil.ClusterServiceVersionStatus() + return k8sClient.Status().Update(ctx, jaegerCSV) }, testutil.Timeout, testutil.Interval).Should(Succeed()) - jenkinsCSV := testutil.ClusterServiceVersion("jenkins-csv.v0.0.1", operatorNamespaceName, testutil.JenkinsExample) - Expect(k8sClient.Create(ctx, jenkinsCSV)).Should(Succeed()) + mongodbCSV := testutil.ClusterServiceVersion("mongodb-atlas-kubernetes-csv.v0.0.1", "mongodb-atlas-kubernetes", operatorNamespaceName, testutil.MongodbExample) + Expect(k8sClient.Create(ctx, mongodbCSV)).Should(Succeed()) Eventually(func() error { - k8sClient.Get(ctx, types.NamespacedName{Name: "jenkins-csv.v0.0.1", Namespace: operatorNamespaceName}, jenkinsCSV) - jenkinsCSV.Status = testutil.ClusterServiceVersionStatus() - return k8sClient.Status().Update(ctx, jenkinsCSV) + k8sClient.Get(ctx, types.NamespacedName{Name: "mongodb-atlas-kubernetes-csv.v0.0.1", Namespace: operatorNamespaceName}, mongodbCSV) + mongodbCSV.Status = testutil.ClusterServiceVersionStatus() + return k8sClient.Status().Update(ctx, mongodbCSV) }, testutil.Timeout, testutil.Interval).Should(Succeed()) By("Creating and Setting status of the InstallPlan") - etcdIP := testutil.InstallPlan("etcd-install-plan", operatorNamespaceName) - Expect(k8sClient.Create(ctx, etcdIP)).Should(Succeed()) + jaegerIP := testutil.InstallPlan("jaeger-install-plan", operatorNamespaceName) + Expect(k8sClient.Create(ctx, jaegerIP)).Should(Succeed()) Eventually(func() error { - k8sClient.Get(ctx, types.NamespacedName{Name: "etcd-install-plan", Namespace: operatorNamespaceName}, etcdIP) - etcdIP.Status = testutil.InstallPlanStatus() - return k8sClient.Status().Update(ctx, etcdIP) + k8sClient.Get(ctx, types.NamespacedName{Name: "jaeger-install-plan", Namespace: operatorNamespaceName}, jaegerIP) + jaegerIP.Status = testutil.InstallPlanStatus() + return k8sClient.Status().Update(ctx, jaegerIP) }, testutil.Timeout, testutil.Interval).Should(Succeed()) - jenkinsIP := testutil.InstallPlan("jenkins-install-plan", operatorNamespaceName) - Expect(k8sClient.Create(ctx, jenkinsIP)).Should(Succeed()) + mongodbIP := testutil.InstallPlan("mongodb-atlas-kubernetes-install-plan", operatorNamespaceName) + Expect(k8sClient.Create(ctx, mongodbIP)).Should(Succeed()) Eventually(func() error { - k8sClient.Get(ctx, types.NamespacedName{Name: "jenkins-install-plan", Namespace: operatorNamespaceName}, jenkinsIP) - jenkinsIP.Status = testutil.InstallPlanStatus() - return k8sClient.Status().Update(ctx, jenkinsIP) + k8sClient.Get(ctx, types.NamespacedName{Name: "mongodb-atlas-kubernetes-install-plan", Namespace: operatorNamespaceName}, mongodbIP) + mongodbIP.Status = testutil.InstallPlanStatus() + return k8sClient.Status().Update(ctx, mongodbIP) }, testutil.Timeout, testutil.Interval).Should(Succeed()) - By("Checking of the CR of the etcd operator") + By("Checking of the CR of the jaeger operator") Eventually(func() error { - etcdCluster := &v1beta2.EtcdCluster{} - err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: "example", Namespace: registryNamespaceName}, etcdCluster) + jaegerCR := &jaegerv1.Jaeger{} + err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: "my-jaeger", Namespace: registryNamespaceName}, jaegerCR) return err }, testutil.Timeout, testutil.Interval).Should(Succeed()) - By("Checking of the k8s resource of the etcd operator") + By("Checking of the k8s resource of the jaeger operator") Eventually(func() error { - etcdConfigMap := &corev1.ConfigMap{} - err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: "jenkins-configmap", Namespace: registryNamespaceName}, etcdConfigMap) + jaegerConfigMap := &corev1.ConfigMap{} + err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: "jaeger-configmap", Namespace: registryNamespaceName}, jaegerConfigMap) return err }, testutil.Timeout, testutil.Interval).Should(Succeed()) - By("Disabling the etcd operator from first OperandRequest") + Eventually(func() error { + jaegerConfigMap := &corev1.ConfigMap{} + err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: "jaeger-configmap-reference", Namespace: registryNamespaceName}, jaegerConfigMap) + return err + }, testutil.Timeout, testutil.Interval).Should(Succeed()) + + By("Checking the status of first OperandRequest") + Eventually(func() operatorv1alpha1.ClusterPhase { + requestInstance1 := &operatorv1alpha1.OperandRequest{} + Expect(k8sClient.Get(ctx, requestKey1, requestInstance1)).Should(Succeed()) + return requestInstance1.Status.Phase + }, testutil.Timeout, testutil.Interval).Should(Equal(operatorv1alpha1.ClusterPhaseRunning)) + + By("Disabling the jaeger operator from first OperandRequest") requestInstance1 := &operatorv1alpha1.OperandRequest{} Expect(k8sClient.Get(ctx, requestKey1, requestInstance1)).Should(Succeed()) requestInstance1.Spec.Requests[0].Operands = requestInstance1.Spec.Requests[0].Operands[1:] @@ -327,12 +340,19 @@ var _ = Describe("OperandRegistry controller", func() { return k8sClient.Update(ctx, requestInstance1) }, testutil.Timeout, testutil.Interval).Should(Succeed()) Eventually(func() error { - etcdCluster := &v1beta2.EtcdCluster{} - err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: "example", Namespace: registryNamespaceName}, etcdCluster) + jaegerCR := &jaegerv1.Jaeger{} + err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: "my-jaeger", Namespace: registryNamespaceName}, jaegerCR) return err }, testutil.Timeout, testutil.Interval).Should(Succeed()) - By("Disabling the etcd operator from second OperandRequest") + By("Checking the status of first OperandRequest after updating the Operand") + Eventually(func() operatorv1alpha1.ClusterPhase { + requestInstance1 := &operatorv1alpha1.OperandRequest{} + Expect(k8sClient.Get(ctx, requestKey1, requestInstance1)).Should(Succeed()) + return requestInstance1.Status.Phase + }, testutil.Timeout, testutil.Interval).Should(Equal(operatorv1alpha1.ClusterPhaseRunning)) + + By("Disabling the jaeger operator from second OperandRequest") requestInstance2 := &operatorv1alpha1.OperandRequest{} Expect(k8sClient.Get(ctx, requestKey2, requestInstance2)).Should(Succeed()) requestInstance2.Spec.Requests[0].Operands = requestInstance2.Spec.Requests[0].Operands[1:] @@ -340,41 +360,54 @@ var _ = Describe("OperandRegistry controller", func() { return k8sClient.Update(ctx, requestInstance2) }, testutil.Timeout, testutil.Interval).Should(Succeed()) Eventually(func() bool { - etcdCluster := &v1beta2.EtcdCluster{} - err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: "example", Namespace: registryNamespaceName}, etcdCluster) + jaegerCR := &jaegerv1.Jaeger{} + err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: "my-jaeger", Namespace: registryNamespaceName}, jaegerCR) return err != nil && errors.IsNotFound(err) }, testutil.Timeout, testutil.Interval).Should(BeTrue()) + By("Checking jaeger k8s resource has been deleted") + Eventually(func() bool { + jaegerConfigMap := &corev1.ConfigMap{} + err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: "jaeger-configmap", Namespace: registryNamespaceName}, jaegerConfigMap) + return err != nil && errors.IsNotFound(err) + }, testutil.Timeout, testutil.Interval).Should(BeTrue()) + Eventually(func() bool { + jaegerConfigMap := &corev1.ConfigMap{} + err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: "jaeger-configmap-reference", Namespace: registryNamespaceName}, jaegerConfigMap) + return err != nil && errors.IsNotFound(err) + }, testutil.Timeout, testutil.Interval).Should(BeTrue()) + + By("Checking jaeger operators have been deleted") + Eventually(func() bool { + jaegerCSV := &olmv1alpha1.ClusterServiceVersion{} + err := k8sClient.Get(ctx, types.NamespacedName{Name: "jaeger-csv.v0.0.1", Namespace: operatorNamespaceName}, jaegerCSV) + return err != nil && errors.IsNotFound(err) + }, testutil.Timeout, testutil.Interval).Should(BeTrue()) + + By("Checking the status of second OperandRequest after updating the Operand") + Eventually(func() operatorv1alpha1.ClusterPhase { + requestInstance2 := &operatorv1alpha1.OperandRequest{} + Expect(k8sClient.Get(ctx, requestKey2, requestInstance2)).Should(Succeed()) + return requestInstance2.Status.Phase + }, testutil.Timeout, testutil.Interval).Should(Equal(operatorv1alpha1.ClusterPhaseRunning)) + By("Deleting the first OperandRequest") Expect(k8sClient.Delete(ctx, request1)).Should(Succeed()) - By("Checking jenkins operator has not been deleted") + By("Checking mongodb operator has not been deleted") Eventually(func() error { - jenkinsCSV := &olmv1alpha1.ClusterServiceVersion{} - err := k8sClient.Get(ctx, types.NamespacedName{Name: "jenkins-csv.v0.0.1", Namespace: operatorNamespaceName}, jenkinsCSV) + mongodbCSV := &olmv1alpha1.ClusterServiceVersion{} + err := k8sClient.Get(ctx, types.NamespacedName{Name: "mongodb-atlas-kubernetes-csv.v0.0.1", Namespace: operatorNamespaceName}, mongodbCSV) return err }, testutil.Timeout, testutil.Interval).Should(Succeed()) By("Deleting the second OperandRequest") Expect(k8sClient.Delete(ctx, request2)).Should(Succeed()) - By("Checking the k8s resource has been deleted") - Eventually(func() bool { - etcdConfigMap := &corev1.ConfigMap{} - err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: "jenkins-configmap", Namespace: registryNamespaceName}, etcdConfigMap) - return err != nil && errors.IsNotFound(err) - }, testutil.Timeout, testutil.Interval).Should(BeTrue()) - - By("Checking operators have been deleted") - Eventually(func() bool { - etcdCSV := &olmv1alpha1.ClusterServiceVersion{} - err := k8sClient.Get(ctx, types.NamespacedName{Name: "etcd-csv.v0.0.1", Namespace: operatorNamespaceName}, etcdCSV) - return err != nil && errors.IsNotFound(err) - }, testutil.Timeout, testutil.Interval).Should(BeTrue()) - + By("Checking the mongodb-atlas operator has been deleted") Eventually(func() bool { - jenkinsCSV := &olmv1alpha1.ClusterServiceVersion{} - err := k8sClient.Get(ctx, types.NamespacedName{Name: "jenkins-csv.v0.0.1", Namespace: operatorNamespaceName}, jenkinsCSV) + mongodbCSV := &olmv1alpha1.ClusterServiceVersion{} + err := k8sClient.Get(ctx, types.NamespacedName{Name: "mongodb-atlas-kubernetes-csv.v0.0.1", Namespace: operatorNamespaceName}, mongodbCSV) return err != nil && errors.IsNotFound(err) }, testutil.Timeout, testutil.Interval).Should(BeTrue()) @@ -408,64 +441,64 @@ var _ = Describe("OperandRegistry controller", func() { By("Setting status of the Subscriptions") Eventually(func() error { - etcdSub := &olmv1alpha1.Subscription{} - Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "etcd", Namespace: operatorNamespaceName}, etcdSub)).Should(Succeed()) - etcdSub.Status = testutil.SubscriptionStatus("etcd", operatorNamespaceName, "0.0.1") - return k8sClient.Status().Update(ctx, etcdSub) + jaegerSub := &olmv1alpha1.Subscription{} + Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "jaeger", Namespace: operatorNamespaceName}, jaegerSub)).Should(Succeed()) + jaegerSub.Status = testutil.SubscriptionStatus("jaeger", operatorNamespaceName, "0.0.1") + return k8sClient.Status().Update(ctx, jaegerSub) }, testutil.Timeout, testutil.Interval).Should(Succeed()) Eventually(func() error { - jenkinsSub := &olmv1alpha1.Subscription{} - Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "jenkins", Namespace: operatorNamespaceName}, jenkinsSub)).Should(Succeed()) - jenkinsSub.Status = testutil.SubscriptionStatus("jenkins", operatorNamespaceName, "0.0.1") - return k8sClient.Status().Update(ctx, jenkinsSub) + mongodbSub := &olmv1alpha1.Subscription{} + Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "mongodb-atlas-kubernetes", Namespace: operatorNamespaceName}, mongodbSub)).Should(Succeed()) + mongodbSub.Status = testutil.SubscriptionStatus("mongodb-atlas-kubernetes", operatorNamespaceName, "0.0.1") + return k8sClient.Status().Update(ctx, mongodbSub) }, testutil.Timeout, testutil.Interval).Should(Succeed()) By("Creating and Setting status of the ClusterServiceVersions") - etcdCSV := testutil.ClusterServiceVersion("etcd-csv.v0.0.1", operatorNamespaceName, testutil.EtcdExample) - Expect(k8sClient.Create(ctx, etcdCSV)).Should(Succeed()) + jaegerCSV := testutil.ClusterServiceVersion("jaeger-csv.v0.0.1", "jaeger", operatorNamespaceName, testutil.JaegerExample) + Expect(k8sClient.Create(ctx, jaegerCSV)).Should(Succeed()) Eventually(func() error { - k8sClient.Get(ctx, types.NamespacedName{Name: "etcd-csv.v0.0.1", Namespace: operatorNamespaceName}, etcdCSV) - etcdCSV.Status = testutil.ClusterServiceVersionStatus() - return k8sClient.Status().Update(ctx, etcdCSV) + k8sClient.Get(ctx, types.NamespacedName{Name: "jaeger-csv.v0.0.1", Namespace: operatorNamespaceName}, jaegerCSV) + jaegerCSV.Status = testutil.ClusterServiceVersionStatus() + return k8sClient.Status().Update(ctx, jaegerCSV) }, testutil.Timeout, testutil.Interval).Should(Succeed()) - jenkinsCSV := testutil.ClusterServiceVersion("jenkins-csv.v0.0.1", operatorNamespaceName, testutil.JenkinsExample) - Expect(k8sClient.Create(ctx, jenkinsCSV)).Should(Succeed()) + mongodbCSV := testutil.ClusterServiceVersion("mongodb-atlas-kubernetes-csv.v0.0.1", "mongodb-atlas-kubernetes", operatorNamespaceName, testutil.MongodbExample) + Expect(k8sClient.Create(ctx, mongodbCSV)).Should(Succeed()) Eventually(func() error { - k8sClient.Get(ctx, types.NamespacedName{Name: "jenkins-csv.v0.0.1", Namespace: operatorNamespaceName}, jenkinsCSV) - jenkinsCSV.Status = testutil.ClusterServiceVersionStatus() - return k8sClient.Status().Update(ctx, jenkinsCSV) + k8sClient.Get(ctx, types.NamespacedName{Name: "mongodb-atlas-kubernetes-csv.v0.0.1", Namespace: operatorNamespaceName}, mongodbCSV) + mongodbCSV.Status = testutil.ClusterServiceVersionStatus() + return k8sClient.Status().Update(ctx, mongodbCSV) }, testutil.Timeout, testutil.Interval).Should(Succeed()) By("Creating and Setting status of the InstallPlan") - etcdIP := testutil.InstallPlan("etcd-install-plan", operatorNamespaceName) - Expect(k8sClient.Create(ctx, etcdIP)).Should(Succeed()) + jaegerIP := testutil.InstallPlan("jaeger-install-plan", operatorNamespaceName) + Expect(k8sClient.Create(ctx, jaegerIP)).Should(Succeed()) Eventually(func() error { - k8sClient.Get(ctx, types.NamespacedName{Name: "etcd-install-plan", Namespace: operatorNamespaceName}, etcdIP) - etcdIP.Status = testutil.InstallPlanStatus() - return k8sClient.Status().Update(ctx, etcdIP) + k8sClient.Get(ctx, types.NamespacedName{Name: "jaeger-install-plan", Namespace: operatorNamespaceName}, jaegerIP) + jaegerIP.Status = testutil.InstallPlanStatus() + return k8sClient.Status().Update(ctx, jaegerIP) }, testutil.Timeout, testutil.Interval).Should(Succeed()) - jenkinsIP := testutil.InstallPlan("jenkins-install-plan", operatorNamespaceName) - Expect(k8sClient.Create(ctx, jenkinsIP)).Should(Succeed()) + mongodbIP := testutil.InstallPlan("mongodb-atlas-kubernetes-install-plan", operatorNamespaceName) + Expect(k8sClient.Create(ctx, mongodbIP)).Should(Succeed()) Eventually(func() error { - k8sClient.Get(ctx, types.NamespacedName{Name: "jenkins-install-plan", Namespace: operatorNamespaceName}, jenkinsIP) - jenkinsIP.Status = testutil.InstallPlanStatus() - return k8sClient.Status().Update(ctx, jenkinsIP) + k8sClient.Get(ctx, types.NamespacedName{Name: "mongodb-atlas-kubernetes-install-plan", Namespace: operatorNamespaceName}, mongodbIP) + mongodbIP.Status = testutil.InstallPlanStatus() + return k8sClient.Status().Update(ctx, mongodbIP) }, testutil.Timeout, testutil.Interval).Should(Succeed()) // Check subscription Eventually(func() bool { - etcdSub := &olmv1alpha1.Subscription{} - Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "etcd", Namespace: operatorNamespaceName}, etcdSub)).Should(Succeed()) - return (etcdSub != nil) + jaegerSub := &olmv1alpha1.Subscription{} + Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "jaeger", Namespace: operatorNamespaceName}, jaegerSub)).Should(Succeed()) + return (jaegerSub != nil) }, testutil.Timeout, testutil.Interval).Should(BeTrue()) Eventually(func() bool { - etcdSub := &olmv1alpha1.Subscription{} - Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "etcd", Namespace: operatorNamespaceName}, etcdSub)).Should(Succeed()) - return reflect.DeepEqual(etcdSub.Spec.Config, testutil.SubConfig) + jaegerSub := &olmv1alpha1.Subscription{} + Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "jaeger", Namespace: operatorNamespaceName}, jaegerSub)).Should(Succeed()) + return cmp.Equal(jaegerSub.Spec.Config, testutil.SubConfig) }, testutil.Timeout, testutil.Interval).Should(BeTrue()) By("Deleting the OperandConfig") diff --git a/controllers/operandrequest/operandrequest_suite_test.go b/controllers/operandrequest/operandrequest_suite_test.go index f213aa8c..e7a864b6 100644 --- a/controllers/operandrequest/operandrequest_suite_test.go +++ b/controllers/operandrequest/operandrequest_suite_test.go @@ -22,24 +22,24 @@ import ( "testing" "time" - etcdv1beta2 "github.com/coreos/etcd-operator/pkg/apis/etcd/v1beta2" + jaegerv1 "github.com/jaegertracing/jaeger-operator/apis/v1" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/onsi/gomega/gexec" olmv1 "github.com/operator-framework/api/pkg/operators/v1" olmv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" + operatorsv1 "github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/apis/operators/v1" clientgoscheme "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/envtest" - "sigs.k8s.io/controller-runtime/pkg/envtest/printer" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" nssv1 "github.com/IBM/ibm-namespace-scope-operator/api/v1" - apiv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/api/v1alpha1" - deploy "github.com/IBM/operand-deployment-lifecycle-manager/controllers/operator" + apiv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/v4/api/v1alpha1" + deploy "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/operator" // +kubebuilder:scaffold:imports ) @@ -57,9 +57,8 @@ var ( func TestOperanRequest(t *testing.T) { RegisterFailHandler(Fail) - RunSpecsWithDefaultAndCustomReporters(t, - "OperandRequest Controller Suite", - []Reporter{printer.NewlineReporter{}}) + RunSpecs(t, + "OperandRequest Controller Suite") } var _ = BeforeSuite(func(done Done) { @@ -86,7 +85,9 @@ var _ = BeforeSuite(func(done Done) { Expect(err).NotTo(HaveOccurred()) err = olmv1.AddToScheme(clientgoscheme.Scheme) Expect(err).NotTo(HaveOccurred()) - err = etcdv1beta2.AddToScheme(clientgoscheme.Scheme) + err = jaegerv1.AddToScheme(clientgoscheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = operatorsv1.AddToScheme(clientgoscheme.Scheme) Expect(err).NotTo(HaveOccurred()) k8sClient, err = client.New(cfg, client.Options{Scheme: clientgoscheme.Scheme}) diff --git a/controllers/operandrequest/reconcile_operand.go b/controllers/operandrequest/reconcile_operand.go index ab0559c0..128b856e 100644 --- a/controllers/operandrequest/reconcile_operand.go +++ b/controllers/operandrequest/reconcile_operand.go @@ -22,17 +22,19 @@ import ( "encoding/hex" "encoding/json" "fmt" - "reflect" - "regexp" "strconv" "strings" "sync" + "time" olmv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" "github.com/pkg/errors" authorizationv1 "k8s.io/api/authorization/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/equality" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" runtime "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -40,10 +42,12 @@ import ( "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/discovery" "k8s.io/klog" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - operatorv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/api/v1alpha1" - constant "github.com/IBM/operand-deployment-lifecycle-manager/controllers/constant" - util "github.com/IBM/operand-deployment-lifecycle-manager/controllers/util" + operatorv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/v4/api/v1alpha1" + constant "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/constant" + util "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/util" ) func (r *Reconciler) reconcileOperand(ctx context.Context, requestInstance *operatorv1alpha1.OperandRequest) *util.MultiErr { @@ -68,13 +72,24 @@ func (r *Reconciler) reconcileOperand(ctx context.Context, requestInstance *oper for i, operand := range req.Operands { - opdRegistry := registryInstance.GetOperator(operand.Name) - if opdRegistry == nil { + opdRegistry, err := r.GetOperandFromRegistry(ctx, registryInstance, operand.Name) + if err != nil { + klog.Warningf("Failed to get the Operand %s from the OperandRegistry %s", operand.Name, registryKey.String()) + merr.Add(errors.Wrapf(err, "failed to get the Operand %s from the OperandRegistry %s", operand.Name, registryKey.String())) + continue + } else if opdRegistry == nil { klog.Warningf("Cannot find %s in the OperandRegistry instance %s in the namespace %s ", operand.Name, req.Registry, req.RegistryNamespace) requestInstance.SetMemberStatus(operand.Name, operatorv1alpha1.OperatorNotFound, operatorv1alpha1.ServiceNotFound, &r.Mutex) continue } + // Set no-op operator to Running status + if opdRegistry.InstallMode == operatorv1alpha1.InstallModeNoop { + requestInstance.SetNoSuitableRegistryCondition(registryKey.String(), opdRegistry.Name+" is in maintenance status", operatorv1alpha1.ResourceTypeOperandRegistry, corev1.ConditionTrue, &r.Mutex) + requestInstance.SetMemberStatus(operand.Name, operatorv1alpha1.OperatorRunning, operatorv1alpha1.ServiceRunning, &r.Mutex) + continue + } + operatorName := opdRegistry.Name klog.V(3).Info("Looking for csv for the operator: ", operatorName) @@ -82,70 +97,68 @@ func (r *Reconciler) reconcileOperand(ctx context.Context, requestInstance *oper // Looking for the CSV namespace := r.GetOperatorNamespace(opdRegistry.InstallMode, opdRegistry.Namespace) - sub, err := r.GetSubscription(ctx, operatorName, namespace, opdRegistry.PackageName) - + sub, err := r.GetSubscription(ctx, operatorName, namespace, registryInstance.Namespace, opdRegistry.PackageName) if err != nil { - if apierrors.IsNotFound(err) || sub == nil { - klog.Warningf("There is no Subscription %s or %s in the namespace %s", operatorName, opdRegistry.PackageName, namespace) - continue - } - merr.Add(errors.Wrapf(err, "failed to get the Subscription %s in the namespace %s", operatorName, namespace)) + merr.Add(errors.Wrapf(err, "failed to get the Subscription %s in the namespace %s and %s", operatorName, namespace, registryInstance.Namespace)) return merr } - if _, ok := sub.Labels[constant.OpreqLabel]; !ok { - // Subscription existing and not managed by OperandRequest controller - klog.Warningf("Subscription %s in the namespace %s isn't created by ODLM", sub.Name, sub.Namespace) - } + if !opdRegistry.UserManaged { + if sub == nil { + klog.Warningf("There is no Subscription %s or %s in the namespace %s and %s", operatorName, opdRegistry.PackageName, namespace, registryInstance.Namespace) + continue + } - // For singleton services, identify latest OperandRegistry/Config version has the priority to reconcile - if CheckSingletonServices(operatorName) { - // v1IsLarger is true if subscription has larger channel version than the version in OperandRegistry - // Skip this operator CR creation because it does not have the latest version in OperandRegistry - v1IsLarger, convertErr := util.CompareChannelVersion(sub.Spec.Channel, opdRegistry.Channel) - if convertErr != nil { - merr.Add(errors.Wrapf(err, "failed to compare channel version for the Subscription %s in the namespace %s", operatorName, namespace)) - return merr + if _, ok := sub.Labels[constant.OpreqLabel]; !ok { + // Subscription existing and not managed by OperandRequest controller + klog.Warningf("Subscription %s in the namespace %s isn't created by ODLM", sub.Name, sub.Namespace) } - if v1IsLarger { - klog.V(2).Infof("Subscription %s in the namespace %s is managed by other OperandRequest with newer version %s", sub.Name, sub.Namespace, sub.Spec.Channel) - requestInstance.SetMemberStatus(operand.Name, operatorv1alpha1.OperatorRunning, "", &r.Mutex) + + // It the installplan is not created yet, ODLM will try later + if sub.Status.Install == nil || sub.Status.InstallPlanRef.Name == "" { + klog.Warningf("The Installplan for Subscription %s is not ready. Will check it again", sub.Name) + requestInstance.SetMemberStatus(operand.Name, operatorv1alpha1.OperatorInstalling, "", &r.Mutex) continue } - } - // It the installplan is not created yet, ODLM will try later - if sub.Status.Install == nil || sub.Status.InstallPlanRef.Name == "" { - klog.Warningf("The Installplan for Subscription %s is not ready. Will check it again", sub.Name) - requestInstance.SetMemberStatus(operand.Name, operatorv1alpha1.OperatorInstalling, "", &r.Mutex) - continue - } - - // If the installplan is deleted after is completed, ODLM won't block the CR update. - ipName := sub.Status.InstallPlanRef.Name - ipNamespace := sub.Namespace - ip := &olmv1alpha1.InstallPlan{} - ipKey := types.NamespacedName{ - Name: ipName, - Namespace: ipNamespace, - } - if err := r.Client.Get(ctx, ipKey, ip); err != nil { - if !apierrors.IsNotFound(err) { - merr.Add(errors.Wrapf(err, "failed to get Installplan")) + // If the installplan is deleted after is completed, ODLM won't block the CR update. + ipName := sub.Status.InstallPlanRef.Name + ipNamespace := sub.Namespace + ip := &olmv1alpha1.InstallPlan{} + ipKey := types.NamespacedName{ + Name: ipName, + Namespace: ipNamespace, } - } else if ip.Status.Phase == olmv1alpha1.InstallPlanPhaseFailed { - klog.Errorf("installplan %s/%s is failed", ipNamespace, ipName) - requestInstance.SetMemberStatus(operand.Name, operatorv1alpha1.OperatorFailed, "", &r.Mutex) - continue + if err := r.Client.Get(ctx, ipKey, ip); err != nil { + if !apierrors.IsNotFound(err) { + merr.Add(errors.Wrapf(err, "failed to get Installplan")) + } + } else if ip.Status.Phase == olmv1alpha1.InstallPlanPhaseFailed { + klog.Errorf("installplan %s/%s is failed", ipNamespace, ipName) + requestInstance.SetMemberStatus(operand.Name, operatorv1alpha1.OperatorFailed, "", &r.Mutex) + continue + } + } - csv, err := r.GetClusterServiceVersion(ctx, sub) + var csv *olmv1alpha1.ClusterServiceVersion - // If can't get CSV, requeue the request - if err != nil { - merr.Add(err) - requestInstance.SetMemberStatus(operand.Name, operatorv1alpha1.OperatorFailed, "", &r.Mutex) - continue + if opdRegistry.UserManaged { + csvList, err := r.GetClusterServiceVersionListFromPackage(ctx, opdRegistry.PackageName, opdRegistry.Namespace) + if err != nil { + merr.Add(err) + requestInstance.SetMemberStatus(operand.Name, operatorv1alpha1.OperatorFailed, "", &r.Mutex) + continue + } + csv = csvList[0] + } else { + csv, err = r.GetClusterServiceVersion(ctx, sub) + // If can't get CSV, requeue the request + if err != nil { + merr.Add(err) + requestInstance.SetMemberStatus(operand.Name, operatorv1alpha1.OperatorFailed, "", &r.Mutex) + continue + } } if csv == nil { @@ -154,24 +167,25 @@ func (r *Reconciler) reconcileOperand(ctx context.Context, requestInstance *oper continue } - klog.V(3).Info("Generating customresource base on ClusterServiceVersion: ", csv.GetName()) - requestInstance.SetMemberStatus(operand.Name, operatorv1alpha1.OperatorRunning, "", &r.Mutex) + if err := r.DeleteRedundantCSV(ctx, csv.Name, csv.Namespace, registryKey.Namespace, opdRegistry.PackageName); err != nil { + merr.Add(errors.Wrapf(err, "failed to delete the redundant ClusterServiceVersion %s in the namespace %s", csv.Name, csv.Namespace)) + requestInstance.SetMemberStatus(operand.Name, operatorv1alpha1.OperatorFailed, "", &r.Mutex) + continue + } - // find the OperandRequest which has the same operator's channel version as existing subscription. - // ODLM will only reconcile Operand based on OperandConfig for this OperandRequest - var requestList []string - reg, _ := regexp.Compile(`^(.*)\.(.*)\/request`) - for anno, version := range sub.Annotations { - if reg.MatchString(anno) && version == sub.Spec.Channel { - requestList = append(requestList, anno) + if !opdRegistry.UserManaged { + // find the OperandRequest which has the same operator's channel or fallback channels as existing subscription. + // ODLM will only reconcile Operand based on OperandConfig for this OperandRequest + channels := []string{opdRegistry.Channel} + if channels = append(channels, opdRegistry.FallbackChannels...); !util.Contains(channels, sub.Spec.Channel) { + klog.Infof("Subscription %s in the namespace %s is NOT managed by %s/%s, Skip reconciling Operands", sub.Name, sub.Namespace, requestInstance.Namespace, requestInstance.Name) + requestInstance.SetMemberStatus(operand.Name, operatorv1alpha1.OperatorFailed, "", &r.Mutex) + continue } } - if len(requestList) == 0 || !util.Contains(requestList, requestInstance.Namespace+"."+requestInstance.Name+"/request") { - klog.V(2).Infof("Subscription %s in the namespace %s is NOT managed by %s/%s, Skip reconciling Operands", sub.Name, sub.Namespace, requestInstance.Namespace, requestInstance.Name) - requestInstance.SetMemberStatus(operand.Name, "", operatorv1alpha1.ServiceRunning, &r.Mutex) - continue - } + klog.V(3).Info("Generating customresource base on ClusterServiceVersion: ", csv.GetName()) + requestInstance.SetMemberStatus(operand.Name, operatorv1alpha1.OperatorRunning, "", &r.Mutex) // Merge and Generate CR if operand.Kind == "" { @@ -179,11 +193,11 @@ func (r *Reconciler) reconcileOperand(ctx context.Context, requestInstance *oper if err == nil { // Check the requested Service Config if exist in specific OperandConfig opdConfig := configInstance.GetService(operand.Name) - if opdConfig == nil { + if opdConfig == nil && !opdRegistry.UserManaged { klog.V(2).Infof("There is no service: %s from the OperandConfig instance: %s/%s, Skip reconciling Operands", operand.Name, registryKey.Namespace, req.Registry) continue } - err = r.reconcileCRwithConfig(ctx, opdConfig, configInstance.Namespace, csv, requestInstance, sub.Namespace, &r.Mutex) + err = r.reconcileCRwithConfig(ctx, opdConfig, configInstance.Name, configInstance.Namespace, csv, requestInstance, operand.Name, csv.Namespace, &r.Mutex) if err != nil { merr.Add(err) requestInstance.SetMemberStatus(operand.Name, "", operatorv1alpha1.ServiceFailed, &r.Mutex) @@ -197,7 +211,7 @@ func (r *Reconciler) reconcileOperand(ctx context.Context, requestInstance *oper } } else { - err = r.reconcileCRwithRequest(ctx, requestInstance, operand, types.NamespacedName{Name: requestInstance.Name, Namespace: requestInstance.Namespace}, i, sub.Namespace, &r.Mutex) + err = r.reconcileCRwithRequest(ctx, requestInstance, operand, types.NamespacedName{Name: requestInstance.Name, Namespace: requestInstance.Namespace}, i, csv.Namespace, &r.Mutex) if err != nil { merr.Add(err) requestInstance.SetMemberStatus(operand.Name, "", operatorv1alpha1.ServiceFailed, &r.Mutex) @@ -215,64 +229,36 @@ func (r *Reconciler) reconcileOperand(ctx context.Context, requestInstance *oper } // reconcileCRwithConfig merge and create custom resource base on OperandConfig and CSV alm-examples -func (r *Reconciler) reconcileCRwithConfig(ctx context.Context, service *operatorv1alpha1.ConfigService, namespace string, csv *olmv1alpha1.ClusterServiceVersion, requestInstance *operatorv1alpha1.OperandRequest, operatorNamespace string, mu sync.Locker) error { +func (r *Reconciler) reconcileCRwithConfig(ctx context.Context, service *operatorv1alpha1.ConfigService, opConfigName, opConfigNs string, csv *olmv1alpha1.ClusterServiceVersion, requestInstance *operatorv1alpha1.OperandRequest, operandName string, operatorNamespace string, mu sync.Locker) error { merr := &util.MultiErr{} // Create k8s resources required by service if service.Resources != nil { - for _, res := range service.Resources { - if res.APIVersion == "" { - return fmt.Errorf("The APIVersion of k8s resource is empty for operator " + service.Name) - } - - if res.Kind == "" { - return fmt.Errorf("The Kind of k8s resource is empty for operator " + service.Name) - } - if res.Name == "" { - return fmt.Errorf("The Name of k8s resource is empty for operator " + service.Name) - } - var k8sResNs string - if res.Namespace == "" { - k8sResNs = namespace - } else { - k8sResNs = res.Namespace - } - - var k8sRes unstructured.Unstructured - k8sRes.SetAPIVersion(res.APIVersion) - k8sRes.SetKind(res.Kind) - k8sRes.SetName(res.Name) - k8sRes.SetNamespace(k8sResNs) - - verbs := []string{"create", "delete", "get", "update"} - if r.checkResAuth(ctx, verbs, k8sRes) { - err := r.Client.Get(ctx, types.NamespacedName{ - Name: res.Name, - Namespace: k8sResNs, - }, &k8sRes) - - if err != nil && !apierrors.IsNotFound(err) { - merr.Add(errors.Wrapf(err, "failed to get k8s resource %s/%s", k8sResNs, res.Name)) - } else if apierrors.IsNotFound(err) { - if err := r.createK8sResource(ctx, k8sRes, res.Data, res.Labels, res.Annotations); err != nil { - merr.Add(err) - } - } else { - if r.CheckLabel(k8sRes, map[string]string{constant.OpreqLabel: "true"}) && res.Force { - // Update k8s resource - klog.V(3).Info("Found existing k8s resource: " + res.Name) - if err := r.updateK8sResource(ctx, k8sRes, res.Data, res.Labels, res.Annotations); err != nil { - merr.Add(err) - } - } else { - klog.V(2).Infof("Skip the k8s resource %s/%s which is not created by ODLM", res.Kind, res.Name) - } + // Get the chunk size + var chunkSize int + if r.StepSize > 0 { + chunkSize = r.StepSize + } else { + chunkSize = 1 + } + var wg sync.WaitGroup + semaphore := make(chan struct{}, chunkSize) + + for i := range service.Resources { + wg.Add(1) + semaphore <- struct{}{} + go func(res operatorv1alpha1.ConfigResource) { + defer wg.Done() + defer func() { <-semaphore }() // release semaphore + err := r.reconcileK8sResourceWithRetries(ctx, res, service.Name, opConfigName, opConfigNs) + if err != nil { + merr.Add(err) } - } else { - klog.Infof("ODLM doesn't have enough permission to reconcile k8s resource -- Kind: %s, NamespacedName: %s/%s", res.Kind, k8sResNs, res.Name) - } + }(service.Resources[i]) } + wg.Wait() + if len(merr.Errors) != 0 { return merr } @@ -284,7 +270,7 @@ func (r *Reconciler) reconcileCRwithConfig(ctx context.Context, service *operato var almExampleList []interface{} err := json.Unmarshal([]byte(almExamples), &almExampleList) if err != nil { - return errors.Wrapf(err, "failed to convert alm-examples in the Subscription %s/%s to slice", namespace, service.Name) + return errors.Wrapf(err, "failed to convert alm-examples in the Subscription %s/%s to slice", opConfigNs, service.Name) } foundMap := make(map[string]bool) @@ -292,6 +278,22 @@ func (r *Reconciler) reconcileCRwithConfig(ctx context.Context, service *operato foundMap[cr] = false } + serviceObject, err := util.ObjectToNewUnstructured(service) + if err != nil { + klog.Errorf("Failed to convert OperandConfig service object %s to unstructured.Unstructured object", service.Name) + return err + } + + if err := r.ParseValueReferenceInObject(ctx, "spec", serviceObject.Object["spec"], serviceObject.Object, "OperandConfig", opConfigName, opConfigNs); err != nil { + klog.Errorf("Failed to parse value reference for service %s: %v", service.Name, err) + return err + } + // cover unstructured.Unstructured object to original OperandConfig object + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(serviceObject.Object, service); err != nil { + klog.Errorf("Failed to convert unstructured.Unstructured object to service object %s", service.Name) + return err + } + // Merge OperandConfig and ClusterServiceVersion alm-examples for _, almExample := range almExampleList { // Create an unstructured object for CR and check its value @@ -306,7 +308,7 @@ func (r *Reconciler) reconcileCRwithConfig(ctx context.Context, service *operato err := r.Client.Get(ctx, types.NamespacedName{ Name: name, - Namespace: namespace, + Namespace: opConfigNs, }, &crFromALM) foundInConfig := false @@ -321,37 +323,33 @@ func (r *Reconciler) reconcileCRwithConfig(ctx context.Context, service *operato klog.Warningf("%v in the alm-example doesn't exist in the OperandConfig for %v", crFromALM.GetKind(), csv.GetName()) continue } + if err != nil && !apierrors.IsNotFound(err) { - merr.Add(errors.Wrapf(err, "failed to get the custom resource %s/%s", namespace, name)) + merr.Add(errors.Wrapf(err, "failed to get the custom resource %s/%s", opConfigNs, name)) continue } else if apierrors.IsNotFound(err) { // Create Custom Resource - if err := r.compareConfigandExample(ctx, crFromALM, service, namespace); err != nil { + if err := r.compareConfigandExample(ctx, crFromALM, service, opConfigNs); err != nil { merr.Add(err) continue } } else { if r.CheckLabel(crFromALM, map[string]string{constant.OpreqLabel: "true"}) { // Update or Delete Custom Resource - if err := r.existingCustomResource(ctx, crFromALM, spec.(map[string]interface{}), service, namespace); err != nil { + if err := r.existingCustomResource(ctx, crFromALM, spec.(map[string]interface{}), service, opConfigNs); err != nil { merr.Add(err) continue } - managedBy, err := r.getManagedBy(crFromALM) - if err != nil { - return err - } - statusSpec, err := r.getOperandStatus(crFromALM) + statusFromCR, err := r.getOperandStatus(crFromALM) if err != nil { return err } serviceKind := crFromALM.GetKind() - if serviceKind != "OperandRequest" && statusSpec.ObjectName != "" { + if serviceKind != "OperandRequest" && statusFromCR.ObjectName != "" { var resources []operatorv1alpha1.OperandStatus - resources = append(resources, statusSpec) - serviceSpec := newServiceStatus(managedBy, operatorNamespace, resources) - seterr := requestInstance.SetServiceStatus(ctx, serviceSpec, r.Client, mu) - if seterr != nil { + resources = append(resources, statusFromCR) + serviceStatus := newServiceStatus(operandName, operatorNamespace, resources) + if seterr := requestInstance.SetServiceStatus(ctx, serviceStatus, r.Client, mu); seterr != nil { return seterr } } @@ -381,11 +379,11 @@ func (r *Reconciler) reconcileCRwithRequest(ctx context.Context, requestInstance var crFromRequest unstructured.Unstructured if operand.APIVersion == "" { - return fmt.Errorf("The APIVersion of operand is empty for operator " + operand.Name) + return fmt.Errorf("the APIVersion of operand is empty for operator %s", operand.Name) } if operand.Kind == "" { - return fmt.Errorf("The Kind of operand is empty for operator " + operand.Name) + return fmt.Errorf("the Kind of operand is empty for operator %s", operand.Name) } var name string @@ -400,6 +398,10 @@ func (r *Reconciler) reconcileCRwithRequest(ctx context.Context, requestInstance crFromRequest.SetNamespace(requestKey.Namespace) crFromRequest.SetAPIVersion(operand.APIVersion) crFromRequest.SetKind(operand.Kind) + // Set the OperandRequest as the owner of the CR from request + if err := controllerutil.SetOwnerReference(requestInstance, &crFromRequest, r.Scheme); err != nil { + merr.Add(errors.Wrapf(err, "failed to set ownerReference for custom resource %s/%s", requestKey.Namespace, name)) + } err := r.Client.Get(ctx, types.NamespacedName{ Name: name, @@ -418,22 +420,18 @@ func (r *Reconciler) reconcileCRwithRequest(ctx context.Context, requestInstance if r.CheckLabel(crFromRequest, map[string]string{constant.OpreqLabel: "true"}) { // Update or Delete Custom resource klog.V(3).Info("Found existing custom resource: " + operand.Kind) - if err := r.updateCustomResource(ctx, crFromRequest, requestKey.Namespace, operand.Kind, operand.Spec.Raw, map[string]interface{}{}); err != nil { - return err - } - managedBy, err := r.getManagedBy(crFromRequest) - if err != nil { + if err := r.updateCustomResource(ctx, crFromRequest, requestKey.Namespace, operand.Kind, operand.Spec.Raw, map[string]interface{}{}, requestInstance); err != nil { return err } - statusSpec, err := r.getOperandStatus(crFromRequest) + statusFromCR, err := r.getOperandStatus(crFromRequest) if err != nil { return err } - if operand.Kind != "OperandRequest" && statusSpec.ObjectName != "" { + if operand.Kind != "OperandRequest" && statusFromCR.ObjectName != "" { var resources []operatorv1alpha1.OperandStatus - resources = append(resources, statusSpec) - serviceSpec := newServiceStatus(managedBy, operatorNamespace, resources) - seterr := requestInstance.SetServiceStatus(ctx, serviceSpec, r.Client, mu) + resources = append(resources, statusFromCR) + serviceStatus := newServiceStatus(operand.Name, operatorNamespace, resources) + seterr := requestInstance.SetServiceStatus(ctx, serviceStatus, r.Client, mu) if seterr != nil { return seterr } @@ -449,38 +447,6 @@ func (r *Reconciler) reconcileCRwithRequest(ctx context.Context, requestInstance return nil } -func (r *Reconciler) getManagedBy(existingCR unstructured.Unstructured) (string, error) { - byteMetadata, err := json.Marshal(existingCR.Object["metadata"]) - if err != nil { - klog.Error(err) - return "", err - } - var rawMetadata map[string]interface{} - err = json.Unmarshal(byteMetadata, &rawMetadata) - if err != nil { - klog.Error(err) - return "", err - } - byteLabels, err := json.Marshal(rawMetadata["labels"]) - if err != nil { - klog.Error(err) - return "", err - } - var parsedLabels map[string]string - err = json.Unmarshal(byteLabels, &parsedLabels) - if err != nil { - klog.Error(err) - return "", err - } - var managedBy string - for key, value := range parsedLabels { - if key == "app.kubernetes.io/managed-by" { - managedBy = value - } - } - return managedBy, nil -} - func (r *Reconciler) getOperandStatus(existingCR unstructured.Unstructured) (operatorv1alpha1.OperandStatus, error) { var emptyStatus operatorv1alpha1.OperandStatus byteStatus, err := json.Marshal(existingCR.Object["status"]) @@ -528,11 +494,97 @@ func newServiceStatus(operatorName string, namespace string, resources []operato } } serviceSpec.Status = status //TODO logic to determine readiness - // serviceSpec.LastUpdateTime = time.Now().Format(time.RFC3339) serviceSpec.Resources = resources return serviceSpec } +func (r *Reconciler) reconcileK8sResourceWithRetries(ctx context.Context, res operatorv1alpha1.ConfigResource, serviceName, opConfigName, opConfigNs string) error { + var err error + for i := 0; i < int(constant.DefaultCRRetryNumber); i++ { + err = r.reconcileK8sResource(ctx, res, serviceName, opConfigName, opConfigNs) + if err == nil { + return nil + } + klog.Errorf("Failed to reconcile k8s resource -- Kind: %s, NamespacedName: %s/%s with error: %v", res.Kind, res.Namespace, res.Name, err) + if i < int(constant.DefaultCRRetryNumber)-1 { + waitTime := time.Duration((1 << i) * 4 * int(time.Second)) + klog.Warningf("Retry reconcile k8s resource -- Kind: %s, NamespacedName: %s/%s after waiting %v", res.Kind, res.Namespace, res.Name, waitTime) + time.Sleep(waitTime) + } + } + return err +} + +func (r *Reconciler) reconcileK8sResource(ctx context.Context, res operatorv1alpha1.ConfigResource, serviceName, opConfigName, opConfigNs string) error { + if res.APIVersion == "" { + return fmt.Errorf("the APIVersion of k8s resource is empty for operator %s", serviceName) + } + + if res.Kind == "" { + return fmt.Errorf("the Kind of k8s resource is empty for operator %s", serviceName) + } + if res.Name == "" { + return fmt.Errorf("the Name of k8s resource is empty for operator %s", serviceName) + } + var k8sResNs string + if res.Namespace == "" { + k8sResNs = opConfigNs + } else { + k8sResNs = res.Namespace + } + + resObject, err := util.ObjectToNewUnstructured(&res) + if err != nil { + klog.Errorf("Failed to convert %s %s/%s object to unstructured.Unstructured object", res.Kind, k8sResNs, res.Name) + return err + } + + if err := r.ParseValueReferenceInObject(ctx, "data", resObject.Object["data"], resObject.Object, "OperandConfig", opConfigName, opConfigNs); err != nil { + klog.Errorf("Failed to parse value reference in resource %s/%s: %v", k8sResNs, res.Name, err) + return err + } + // cover unstructured.Unstructured object to original OperandConfig object + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(resObject.Object, &res); err != nil { + klog.Errorf("Failed to convert unstructured.Unstructured object to %s %s/%s object", res.Kind, k8sResNs, res.Name) + return err + } + + var k8sRes unstructured.Unstructured + k8sRes.SetAPIVersion(res.APIVersion) + k8sRes.SetKind(res.Kind) + k8sRes.SetName(res.Name) + k8sRes.SetNamespace(k8sResNs) + + verbs := []string{"create", "delete", "get", "update"} + if r.checkResAuth(ctx, verbs, k8sRes) { + err := r.Client.Get(ctx, types.NamespacedName{ + Name: res.Name, + Namespace: k8sResNs, + }, &k8sRes) + + if err != nil && !apierrors.IsNotFound(err) { + return errors.Wrapf(err, "failed to get k8s resource %s/%s", k8sResNs, res.Name) + } else if apierrors.IsNotFound(err) { + if err := r.createK8sResource(ctx, k8sRes, res.Data, res.Labels, res.Annotations, &res.OwnerReferences, &res.OptionalFields); err != nil { + return err + } + } else { + if res.Force { + // Update k8s resource + klog.V(3).Info("Found existing k8s resource: " + res.Name) + if err := r.updateK8sResource(ctx, k8sRes, res.Data, res.Labels, res.Annotations, &res.OwnerReferences, &res.OptionalFields); err != nil { + return err + } + } else { + klog.V(2).Infof("Skip the k8s resource %s %s/%s whose force field is false", res.Kind, k8sResNs, res.Name) + } + } + } else { + klog.Infof("ODLM doesn't have enough permission to reconcile k8s resource -- Kind: %s, NamespacedName: %s/%s", res.Kind, k8sResNs, res.Name) + } + return nil +} + // deleteAllCustomResource remove custom resource base on OperandConfig and CSV alm-examples func (r *Reconciler) deleteAllCustomResource(ctx context.Context, csv *olmv1alpha1.ClusterServiceVersion, requestInstance *operatorv1alpha1.OperandRequest, csc *operatorv1alpha1.OperandConfig, operandName, namespace string) error { @@ -582,6 +634,7 @@ func (r *Reconciler) deleteAllCustomResource(ctx context.Context, csv *olmv1alph wg.Wait() if len(merr.Errors) != 0 { + klog.Errorf("Failed to delete custom resource from OperandRequest for operator %s", operandName) return merr } @@ -616,28 +669,35 @@ func (r *Reconciler) deleteAllCustomResource(ctx context.Context, csv *olmv1alph // Compare the name of OperandConfig and CRD name if strings.EqualFold(kind, crdName) { - err := r.Client.Get(ctx, types.NamespacedName{ - Name: name, - Namespace: namespace, - }, &crTemplate) - if err != nil && !apierrors.IsNotFound(err) { - merr.Add(err) - continue - } - if apierrors.IsNotFound(err) { - klog.V(2).Info("Finish Deleting the CR: " + kind) - continue - } - if r.CheckLabel(crTemplate, map[string]string{constant.OpreqLabel: "true"}) { - wg.Add(1) - go func() { - defer wg.Done() - if err := r.deleteCustomResource(ctx, crTemplate, namespace); err != nil { - r.Mutex.Lock() - defer r.Mutex.Unlock() - merr.Add(err) - } - }() + if r.checkResAuth(ctx, []string{"get"}, crTemplate) { + + err := r.Client.Get(ctx, types.NamespacedName{ + Name: name, + Namespace: namespace, + }, &crTemplate) + if err != nil && !apierrors.IsNotFound(err) { + merr.Add(err) + continue + } + if apierrors.IsNotFound(err) { + klog.V(2).Info("Finish Deleting the CR: " + kind) + continue + } + if r.CheckLabel(crTemplate, map[string]string{constant.OpreqLabel: "true"}) { + wg.Add(1) + go func() { + defer wg.Done() + if r.checkResAuth(ctx, []string{"delete"}, crTemplate) { + if err := r.deleteCustomResource(ctx, crTemplate, namespace); err != nil { + r.Mutex.Lock() + defer r.Mutex.Unlock() + merr.Add(err) + return + } + } + }() + } + } } @@ -646,6 +706,7 @@ func (r *Reconciler) deleteAllCustomResource(ctx context.Context, csv *olmv1alph } wg.Wait() if len(merr.Errors) != 0 { + klog.Errorf("Failed to delete custom resource from OperandConfig for operator %s", operandName) return merr } @@ -681,7 +742,7 @@ func (r *Reconciler) createCustomResource(ctx context.Context, crTemplate unstru r.EnsureLabel(crTemplate, map[string]string{constant.OpreqLabel: "true"}) - // Creat the CR + // Create the CR crerr := r.Create(ctx, &crTemplate) if crerr != nil && !apierrors.IsAlreadyExists(crerr) { return errors.Wrap(crerr, "failed to create custom resource") @@ -716,7 +777,7 @@ func (r *Reconciler) existingCustomResource(ctx context.Context, existingCR unst return nil } -func (r *Reconciler) updateCustomResource(ctx context.Context, existingCR unstructured.Unstructured, namespace, crName string, crConfig []byte, configFromALM map[string]interface{}) error { +func (r *Reconciler) updateCustomResource(ctx context.Context, existingCR unstructured.Unstructured, namespace, crName string, crConfig []byte, configFromALM map[string]interface{}, owners ...metav1.Object) error { kind := existingCR.GetKind() apiversion := existingCR.GetAPIVersion() @@ -744,6 +805,14 @@ func (r *Reconciler) updateCustomResource(ctx context.Context, existingCR unstru return true, nil } + forceUpdate := false + for _, owner := range owners { + if err := controllerutil.SetOwnerReference(owner, &existingCR, r.Scheme); err != nil { + return false, errors.Wrapf(err, "failed to set ownerReference for custom resource %s/%s", existingCR.GetNamespace(), existingCR.GetName()) + } + forceUpdate = true + } + configFromALMRaw, err := json.Marshal(configFromALM) if err != nil { klog.Error(err) @@ -768,12 +837,12 @@ func (r *Reconciler) updateCustomResource(ctx context.Context, existingCR unstru // Merge spec from update existing CR and OperandConfig spec updatedCRSpec := util.MergeCR(updatedExistingCRRaw, crConfig) - CRgeneration := existingCR.GetGeneration() - - if reflect.DeepEqual(existingCR.Object["spec"], updatedCRSpec) { + if equality.Semantic.DeepEqual(existingCR.Object["spec"], updatedCRSpec) && !forceUpdate { return true, nil } + CRgeneration := existingCR.GetGeneration() + klog.V(2).Infof("updating custom resource with apiversion: %s, kind: %s, %s/%s", apiversion, kind, namespace, name) existingCR.Object["spec"] = updatedCRSpec @@ -846,7 +915,7 @@ func (r *Reconciler) deleteCustomResource(ctx context.Context, existingCR unstru if strings.EqualFold(kind, "OperandRequest") { return true, nil } - klog.V(3).Infof("Waiting for CR %s is removed ...", kind) + klog.V(3).Infof("Waiting for CR %s to be removed ...", kind) err := r.Client.Get(ctx, types.NamespacedName{ Name: name, Namespace: namespace, @@ -936,7 +1005,7 @@ func (r *Reconciler) checkCustomResource(ctx context.Context, requestInstance *o return nil } -func (r *Reconciler) createK8sResource(ctx context.Context, k8sResTemplate unstructured.Unstructured, k8sResConfig *runtime.RawExtension, newLabels, newAnnotations map[string]string) error { +func (r *Reconciler) createK8sResource(ctx context.Context, k8sResTemplate unstructured.Unstructured, k8sResConfig *runtime.RawExtension, newLabels, newAnnotations map[string]string, ownerReferences *[]operatorv1alpha1.OwnerReference, optionalFields *[]operatorv1alpha1.OptionalField) error { kind := k8sResTemplate.GetKind() name := k8sResTemplate.GetName() namespace := k8sResTemplate.GetNamespace() @@ -947,15 +1016,45 @@ func (r *Reconciler) createK8sResource(ctx context.Context, k8sResTemplate unstr if k8sResConfigUnmarshalErr != nil { klog.Errorf("failed to unmarshal k8s Resource Config: %v", k8sResConfigUnmarshalErr) } - for k, v := range k8sResConfigDecoded { k8sResTemplate.Object[k] = v } + + k8sResConfigBytes, err := json.Marshal(k8sResConfigDecoded) + if err != nil { + return errors.Wrap(err, "failed to marshal k8sResConfigDecoded") + } + + // Caculate the hash number of the new created template + _, templateHash := util.CalculateResHashes(nil, k8sResConfigBytes) + + newAnnotations = util.AddHashAnnotation(&k8sResTemplate, constant.K8sHashedData, templateHash, newAnnotations) + + if kind == "Route" { + if host, found := k8sResConfigDecoded["spec"].(map[string]interface{})["host"].(string); found { + hostHash := util.CalculateHash([]byte(host)) + + // if newAnnotations == nil { + // newAnnotations = make(map[string]string) + // } + // newAnnotations[constant.RouteHash] = hostHash + newAnnotations = util.AddHashAnnotation(&k8sResTemplate, constant.RouteHash, hostHash, newAnnotations) + + } else { + klog.Warningf("spec.host not found in Route %s/%s", namespace, name) + } + } } + if err := r.ExecuteOptionalFields(ctx, &k8sResTemplate, optionalFields); err != nil { + return errors.Wrap(err, "failed to execute optional fields") + } r.EnsureLabel(k8sResTemplate, map[string]string{constant.OpreqLabel: "true"}) r.EnsureLabel(k8sResTemplate, newLabels) r.EnsureAnnotation(k8sResTemplate, newAnnotations) + if err := r.setOwnerReferences(ctx, &k8sResTemplate, ownerReferences); err != nil { + return errors.Wrap(err, "failed to set ownerReferences for k8s resource") + } // Create the k8s resource err := r.Create(ctx, &k8sResTemplate) @@ -968,70 +1067,48 @@ func (r *Reconciler) createK8sResource(ctx context.Context, k8sResTemplate unstr return nil } -func (r *Reconciler) updateK8sResource(ctx context.Context, existingK8sRes unstructured.Unstructured, k8sResConfig *runtime.RawExtension, newLabels, newAnnotations map[string]string) error { +func (r *Reconciler) updateK8sResource(ctx context.Context, existingK8sRes unstructured.Unstructured, k8sResConfig *runtime.RawExtension, newLabels, newAnnotations map[string]string, ownerReferences *[]operatorv1alpha1.OwnerReference, optionalFields *[]operatorv1alpha1.OptionalField) error { kind := existingK8sRes.GetKind() apiversion := existingK8sRes.GetAPIVersion() name := existingK8sRes.GetName() namespace := existingK8sRes.GetNamespace() - if kind == "Job" { - existingK8sRes := unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": apiversion, - "kind": kind, - }, - } - - err := r.Client.Get(ctx, types.NamespacedName{ - Name: name, - Namespace: namespace, - }, &existingK8sRes) - if err != nil { - return errors.Wrapf(err, "failed to get k8s resource -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name) - } - if !r.CheckLabel(existingK8sRes, map[string]string{constant.OpreqLabel: "true"}) { - return nil + if kind == "Job" { + if err := r.updateK8sJob(ctx, existingK8sRes, k8sResConfig, newLabels, newAnnotations, ownerReferences, optionalFields); err != nil { + return errors.Wrap(err, "failed to update Job") } + return nil + } - var existingHashedData string - var newHashedData string - if existingK8sRes.GetAnnotations() != nil { - existingHashedData = existingK8sRes.GetAnnotations()[constant.HashedData] + if kind == "Route" { + if err := r.updateK8sRoute(ctx, existingK8sRes, k8sResConfig, newLabels, newAnnotations, ownerReferences, optionalFields); err != nil { + return errors.Wrap(err, "failed to update Route") } + // update the annotations of the Route host if the host is changed if k8sResConfig != nil { - hashedData := sha256.Sum256(k8sResConfig.Raw) - newHashedData = hex.EncodeToString(hashedData[:7]) - } - - if existingHashedData != newHashedData { - // create a new template of k8s resource - var templatek8sRes unstructured.Unstructured - templatek8sRes.SetAPIVersion(apiversion) - templatek8sRes.SetKind(kind) - templatek8sRes.SetName(name) - templatek8sRes.SetNamespace(namespace) - - if newAnnotations == nil { - newAnnotations = make(map[string]string) + k8sResConfigDecoded := make(map[string]interface{}) + if k8sResConfigUnmarshalErr := json.Unmarshal(k8sResConfig.Raw, &k8sResConfigDecoded); k8sResConfigUnmarshalErr != nil { + return errors.Wrap(k8sResConfigUnmarshalErr, "failed to unmarshal k8s Resource Config") } - newAnnotations[constant.HashedData] = newHashedData - if err := r.deleteK8sResource(ctx, existingK8sRes, namespace); err != nil { - return errors.Wrap(err, "failed to update k8s resource") - } - if err := r.createK8sResource(ctx, templatek8sRes, k8sResConfig, newLabels, newAnnotations); err != nil { - return errors.Wrap(err, "failed to update k8s resource") + if host, found := k8sResConfigDecoded["spec"].(map[string]interface{})["host"].(string); found { + hostHash := util.CalculateHash([]byte(host)) + + if newAnnotations == nil { + newAnnotations = make(map[string]string) + } + newAnnotations[constant.RouteHash] = hostHash + } else { + klog.Warningf("spec.host not found in Route %s/%s", namespace, name) } } - - return nil } - // Update the k8s res + // Update the k8s resource err := wait.PollImmediate(constant.DefaultCRFetchPeriod, constant.DefaultCRFetchTimeout, func() (bool, error) { - existingK8sRes := unstructured.Unstructured{ + existingRes := unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": apiversion, "kind": kind, @@ -1041,68 +1118,88 @@ func (r *Reconciler) updateK8sResource(ctx context.Context, existingK8sRes unstr err := r.Client.Get(ctx, types.NamespacedName{ Name: name, Namespace: namespace, - }, &existingK8sRes) - + }, &existingRes) if err != nil { return false, errors.Wrapf(err, "failed to get k8s resource -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name) } - if !r.CheckLabel(existingK8sRes, map[string]string{constant.OpreqLabel: "true"}) { + resourceVersion := existingRes.GetResourceVersion() + + if !r.CheckLabel(existingRes, map[string]string{constant.OpreqLabel: "true"}) && (newLabels == nil || newLabels[constant.OpreqLabel] != "true") { return true, nil } - // isEqual := r.CheckAnnotation(existingK8sRes, newAnnotations) && r.CheckLabel(existingK8sRes, newLabels) if k8sResConfig != nil { - k8sResConfigDecoded := make(map[string]interface{}) - k8sResConfigUnmarshalErr := json.Unmarshal(k8sResConfig.Raw, &k8sResConfigDecoded) - if k8sResConfigUnmarshalErr != nil { - klog.Errorf("failed to unmarshal k8s Resource Config: %v", k8sResConfigUnmarshalErr) - } - for k, v := range k8sResConfigDecoded { - // isEqual = isEqual && reflect.DeepEqual(existingK8sRes.Object[k], v) - existingK8sRes.Object[k] = v + // Convert existing k8s resource to string + existingResRaw, err := json.Marshal(existingRes.Object) + if err != nil { + return false, errors.Wrapf(err, "failed to marshal existing k8s resource -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name) } - } - CRgeneration := existingK8sRes.GetGeneration() + // Caculate the hash number of the new created template + existingHash, templateHash := util.CalculateResHashes(&existingRes, k8sResConfig.Raw) - // if isEqual { - // return true, nil - // } + // If the hash number of the existing k8s resource is different from the hash number of template, update the k8s resource + if existingHash != templateHash { + k8sResConfigDecoded := make(map[string]interface{}) + k8sResConfigUnmarshalErr := json.Unmarshal(k8sResConfig.Raw, &k8sResConfigDecoded) + if k8sResConfigUnmarshalErr != nil { + klog.Errorf("failed to unmarshal k8s Resource Config: %v", k8sResConfigUnmarshalErr) + } + for k, v := range k8sResConfigDecoded { + existingRes.Object[k] = v + } + newAnnotations = util.AddHashAnnotation(&existingRes, constant.K8sHashedData, templateHash, newAnnotations) - r.EnsureAnnotation(existingK8sRes, newAnnotations) - r.EnsureLabel(existingK8sRes, newLabels) + } else { + // If the hash number are the same, then do the deep merge + // Merge the existing CR and the CR from the OperandConfig + updatedExistingRes := util.MergeCR(existingResRaw, k8sResConfig.Raw) + // Update the existing k8s resource with the merged CR + existingRes.Object = updatedExistingRes + } - klog.V(2).Infof("updating k8s resource with apiversion: %s, kind: %s, %s/%s", apiversion, kind, namespace, name) + if err := r.ExecuteOptionalFields(ctx, &existingRes, optionalFields); err != nil { + return false, errors.Wrap(err, "failed to execute optional fields") + } + r.EnsureAnnotation(existingRes, newAnnotations) + r.EnsureLabel(existingRes, newLabels) + if err := r.setOwnerReferences(ctx, &existingRes, ownerReferences); err != nil { + return false, errors.Wrapf(err, "failed to set ownerReferences for k8s resource -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name) + } - err = r.Update(ctx, &existingK8sRes) + klog.Infof("updating k8s resource with apiversion: %s, kind: %s, %s/%s", apiversion, kind, namespace, name) - if err != nil { - return false, errors.Wrapf(err, "failed to update k8s resource -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name) - } + err = r.Update(ctx, &existingRes) + if err != nil { + return false, errors.Wrapf(err, "failed to update k8s resource -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name) + } - UpdatedK8sRes := unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": apiversion, - "kind": kind, - }, - } + UpdatedK8sRes := unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": apiversion, + "kind": kind, + }, + } - err = r.Client.Get(ctx, types.NamespacedName{ - Name: name, - Namespace: namespace, - }, &UpdatedK8sRes) + err = r.Client.Get(ctx, types.NamespacedName{ + Name: name, + Namespace: namespace, + }, &UpdatedK8sRes) - if err != nil { - return false, errors.Wrapf(err, "failed to get k8s resource -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name) + if err != nil { + return false, errors.Wrapf(err, "failed to get k8s resource -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name) - } + } - if UpdatedK8sRes.GetGeneration() != CRgeneration { - klog.V(2).Infof("Finish updating the k8s Resource: -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name) - } + if UpdatedK8sRes.GetResourceVersion() != resourceVersion { + klog.Infof("Finish updating the k8s Resource: -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name) + } else { + klog.Infof("No updates on k8s resource with apiversion: %s, kind: %s, %s/%s", apiversion, kind, namespace, name) + } + } return true, nil }) @@ -1113,52 +1210,122 @@ func (r *Reconciler) updateK8sResource(ctx context.Context, existingK8sRes unstr return nil } -func (r *Reconciler) deleteK8sResource(ctx context.Context, existingK8sRes unstructured.Unstructured, namespace string) error { +func (r *Reconciler) updateK8sJob(ctx context.Context, existingK8sRes unstructured.Unstructured, k8sResConfig *runtime.RawExtension, newLabels, newAnnotations map[string]string, ownerReferences *[]operatorv1alpha1.OwnerReference, optionalFields *[]operatorv1alpha1.OptionalField) error { kind := existingK8sRes.GetKind() apiversion := existingK8sRes.GetAPIVersion() name := existingK8sRes.GetName() + namespace := existingK8sRes.GetNamespace() - k8sResShouldBeDeleted := unstructured.Unstructured{ + existingRes := unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": apiversion, "kind": kind, }, } + err := r.Client.Get(ctx, types.NamespacedName{ Name: name, Namespace: namespace, - }, &k8sResShouldBeDeleted) - if err != nil && !apierrors.IsNotFound(err) { + }, &existingRes) + + if err != nil { return errors.Wrapf(err, "failed to get k8s resource -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name) } - if apierrors.IsNotFound(err) { - klog.V(3).Infof("There is no k8s resource: %s from kind: %s", name, kind) - } else { - if r.CheckLabel(k8sResShouldBeDeleted, map[string]string{constant.OpreqLabel: "true"}) && !r.CheckLabel(k8sResShouldBeDeleted, map[string]string{constant.NotUninstallLabel: "true"}) { - klog.V(3).Infof("Deleting k8s resource: %s from kind: %s", name, kind) - err := r.Delete(ctx, &k8sResShouldBeDeleted) - if err != nil && !apierrors.IsNotFound(err) { - return errors.Wrapf(err, "failed to delete k8s resource -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name) - } - err = wait.PollImmediate(constant.DefaultCRDeletePeriod, constant.DefaultCRDeleteTimeout, func() (bool, error) { - klog.V(3).Infof("Waiting for k8s resource %s is removed ...", kind) - err := r.Client.Get(ctx, types.NamespacedName{ - Name: name, - Namespace: namespace, - }, &existingK8sRes) - if apierrors.IsNotFound(err) { - return true, nil + if !r.CheckLabel(existingRes, map[string]string{constant.OpreqLabel: "true"}) && (newLabels == nil || newLabels[constant.OpreqLabel] != "true") { + return nil + } + + var existingHashedData string + var newHashedData string + if existingRes.GetAnnotations() != nil { + existingHashedData = existingRes.GetAnnotations()[constant.HashedData] + } + + if k8sResConfig != nil { + hashedData := sha256.Sum256(k8sResConfig.Raw) + newHashedData = hex.EncodeToString(hashedData[:7]) + } + + if existingHashedData != newHashedData { + // create a new template of k8s resource + var templatek8sRes unstructured.Unstructured + templatek8sRes.SetAPIVersion(apiversion) + templatek8sRes.SetKind(kind) + templatek8sRes.SetName(name) + templatek8sRes.SetNamespace(namespace) + + if newAnnotations == nil { + newAnnotations = make(map[string]string) + } + newAnnotations[constant.HashedData] = newHashedData + + if err := r.deleteK8sResource(ctx, existingRes, newLabels, namespace); err != nil { + return errors.Wrap(err, "failed to update k8s resource") + } + if err := r.createK8sResource(ctx, templatek8sRes, k8sResConfig, newLabels, newAnnotations, ownerReferences, optionalFields); err != nil { + return errors.Wrap(err, "failed to update k8s resource") + } + } + return nil +} + +// update route resource +func (r *Reconciler) updateK8sRoute(ctx context.Context, existingK8sRes unstructured.Unstructured, k8sResConfig *runtime.RawExtension, newLabels, newAnnotations map[string]string, ownerReferences *[]operatorv1alpha1.OwnerReference, optionalFields *[]operatorv1alpha1.OptionalField) error { + kind := existingK8sRes.GetKind() + apiversion := existingK8sRes.GetAPIVersion() + name := existingK8sRes.GetName() + namespace := existingK8sRes.GetNamespace() + + existingRes := unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": apiversion, + "kind": kind, + }, + } + err := r.Client.Get(ctx, types.NamespacedName{ + Name: name, + Namespace: namespace, + }, &existingRes) + + if err != nil { + return errors.Wrapf(err, "failed to get k8s resource -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name) + } + if !r.CheckLabel(existingRes, map[string]string{constant.OpreqLabel: "true"}) && (newLabels == nil || newLabels[constant.OpreqLabel] != "true") { + return nil + } + + existingAnnos := existingRes.GetAnnotations() + existingHostHash := existingAnnos[constant.RouteHash] + + if k8sResConfig != nil { + k8sResConfigDecoded := make(map[string]interface{}) + k8sResConfigUnmarshalErr := json.Unmarshal(k8sResConfig.Raw, &k8sResConfigDecoded) + if k8sResConfigUnmarshalErr != nil { + klog.Errorf("failed to unmarshal k8s Resource Config: %v", k8sResConfigUnmarshalErr) + } + + // Read the host from the OperandConfig + if newHost, found := k8sResConfigDecoded["spec"].(map[string]interface{})["host"].(string); found { + newHostHash := util.CalculateHash([]byte(newHost)) + + // Only re-create the route if the custom host has been removed + if newHost == "" && existingHostHash != newHostHash { + + // create a new template of k8s resource + var templatek8sRes unstructured.Unstructured + templatek8sRes.SetAPIVersion(apiversion) + templatek8sRes.SetKind(kind) + templatek8sRes.SetName(name) + templatek8sRes.SetNamespace(namespace) + + if err := r.deleteK8sResource(ctx, existingRes, newLabels, namespace); err != nil { + return errors.Wrap(err, "failed to delete Route for recreation") } - if err != nil { - return false, errors.Wrapf(err, "failed to get k8s resource -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name) + if err := r.createK8sResource(ctx, templatek8sRes, k8sResConfig, newLabels, newAnnotations, ownerReferences, optionalFields); err != nil { + return errors.Wrap(err, "failed to update k8s resource") } - return false, nil - }) - if err != nil { - return errors.Wrapf(err, "failed to delete k8s resource -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name) } - klog.V(1).Infof("Finish deleting k8s resource -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name) } } return nil @@ -1197,7 +1364,7 @@ func (r *Reconciler) deleteAllK8sResource(ctx context.Context, csc *operatorv1al wg.Add(1) go func() { defer wg.Done() - if err := r.deleteK8sResource(ctx, k8sResShouldBeDeleted, k8sNamespace); err != nil { + if err := r.deleteK8sResource(ctx, k8sResShouldBeDeleted, k8sRes.Labels, k8sNamespace); err != nil { r.Mutex.Lock() defer r.Mutex.Unlock() merr.Add(err) @@ -1213,6 +1380,66 @@ func (r *Reconciler) deleteAllK8sResource(ctx context.Context, csc *operatorv1al return nil } +func (r *Reconciler) deleteK8sResource(ctx context.Context, existingK8sRes unstructured.Unstructured, newLabels map[string]string, namespace string) error { + + kind := existingK8sRes.GetKind() + apiversion := existingK8sRes.GetAPIVersion() + name := existingK8sRes.GetName() + + k8sResShouldBeDeleted := unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": apiversion, + "kind": kind, + }, + } + err := r.Client.Get(ctx, types.NamespacedName{ + Name: name, + Namespace: namespace, + }, &k8sResShouldBeDeleted) + if err != nil && !apierrors.IsNotFound(err) { + return errors.Wrapf(err, "failed to get k8s resource -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name) + } + if apierrors.IsNotFound(err) { + klog.V(3).Infof("There is no k8s resource: %s from kind: %s", name, kind) + } else { + // If the existing k8s resources has the OpreqLabel and does not have the NotUninstallLabel, delete it + // If the OpreqLabel is difined in OperandConfig resource, delete it + hasOpreqLabel := r.CheckLabel(k8sResShouldBeDeleted, map[string]string{constant.OpreqLabel: "true"}) + hasNotUninstallLabel := r.CheckLabel(k8sResShouldBeDeleted, map[string]string{constant.NotUninstallLabel: "true"}) + opreqLabelInConfig := newLabels != nil && newLabels[constant.OpreqLabel] == "true" + + if (hasOpreqLabel && !hasNotUninstallLabel) || opreqLabelInConfig { + klog.V(3).Infof("Deleting k8s resource: %s from kind: %s", name, kind) + err := r.Delete(ctx, &k8sResShouldBeDeleted, client.PropagationPolicy(metav1.DeletePropagationBackground)) + if err != nil && !apierrors.IsNotFound(err) { + return errors.Wrapf(err, "failed to delete k8s resource -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name) + } + err = wait.PollImmediate(constant.DefaultCRDeletePeriod, constant.DefaultCRDeleteTimeout, func() (bool, error) { + if strings.EqualFold(kind, "OperandRequest") { + return true, nil + } + klog.V(3).Infof("Waiting for resource %s to be removed ...", kind) + err := r.Client.Get(ctx, types.NamespacedName{ + Name: name, + Namespace: namespace, + }, &existingK8sRes) + if apierrors.IsNotFound(err) { + return true, nil + } + if err != nil { + return false, errors.Wrapf(err, "failed to get k8s resource -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name) + } + return false, nil + }) + if err != nil { + return errors.Wrapf(err, "failed to delete k8s resource -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name) + } + klog.V(1).Infof("Finish deleting k8s resource -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name) + } + } + return nil +} + func (r *Reconciler) checkResAuth(ctx context.Context, verbs []string, k8sResTemplate unstructured.Unstructured) bool { kind := k8sResTemplate.GetKind() apiversion := k8sResTemplate.GetAPIVersion() @@ -1273,3 +1500,156 @@ func (r *Reconciler) ResourceForKind(gvk schema.GroupVersionKind, namespace stri } return &mapping.Resource, nil } + +func (r *Reconciler) setOwnerReferences(ctx context.Context, controlledRes *unstructured.Unstructured, ownerReferences *[]operatorv1alpha1.OwnerReference) error { + if ownerReferences != nil { + for _, owner := range *ownerReferences { + ownerObj := unstructured.Unstructured{} + ownerObj.SetAPIVersion(owner.APIVersion) + ownerObj.SetKind(owner.Kind) + ownerObj.SetName(owner.Name) + + if err := r.Reader.Get(ctx, types.NamespacedName{ + Name: owner.Name, + Namespace: controlledRes.GetNamespace(), + }, &ownerObj); err != nil { + return errors.Wrapf(err, "failed to get owner object -- Kind: %s, NamespacedName: %s/%s", owner.Kind, controlledRes.GetNamespace(), owner.Name) + } + if owner.Controller != nil && *owner.Controller { + if err := controllerutil.SetControllerReference(&ownerObj, controlledRes, r.Scheme); err != nil { + return errors.Wrapf(err, "failed to set controller ownerReference for k8s resource -- Kind: %s, NamespacedName: %s/%s", controlledRes.GetKind(), controlledRes.GetNamespace(), controlledRes.GetName()) + } + } else { + if err := controllerutil.SetOwnerReference(&ownerObj, controlledRes, r.Scheme); err != nil { + return errors.Wrapf(err, "failed to set ownerReference for k8s resource -- Kind: %s, NamespacedName: %s/%s", controlledRes.GetKind(), controlledRes.GetNamespace(), controlledRes.GetName()) + } + } + klog.Infof("Set %s with name %s as Owner for k8s resource -- Kind: %s, NamespacedName: %s/%s", owner.Kind, owner.Name, controlledRes.GetKind(), controlledRes.GetNamespace(), controlledRes.GetName()) + } + } + return nil +} + +func (r *Reconciler) ExecuteOptionalFields(ctx context.Context, resTemplate *unstructured.Unstructured, optionalFields *[]operatorv1alpha1.OptionalField) error { + if optionalFields != nil { + for _, field := range *optionalFields { + // Find the path from resTemplate + if value, err := util.SanitizeObjectString(field.Path, resTemplate.Object); err != nil || value == "" { + klog.Warningf("Skipping execute optional field, not find the path %s in the object -- Kind: %s, NamespacedName: %s/%s: %v", field.Path, resTemplate.GetKind(), resTemplate.GetNamespace(), resTemplate.GetName(), err) + continue + } + // Find the match expressions + if field.MatchExpressions != nil { + if !r.findMatchExpressions(ctx, field.MatchExpressions) { + klog.Infof("Skip operation '%s' for optional fields: %v for %s %s/%s", field.Operation, field.Path, resTemplate.GetKind(), resTemplate.GetNamespace(), resTemplate.GetName()) + continue + } + } + // Do operation + switch field.Operation { + case operatorv1alpha1.OperationRemove: + util.RemoveObjectField(resTemplate.Object, field.Path) + // case "Add": # TODO + default: + klog.Warningf("Invalid operation '%s' in optional fields: %v", field.Operation, field) + } + } + } + return nil +} + +func (r *Reconciler) findMatchExpressions(ctx context.Context, matchExpressions []operatorv1alpha1.MatchExpression) bool { + isMatch := false + for _, matchExpression := range matchExpressions { + if matchExpression.Key == "" || matchExpression.Operator == "" || matchExpression.ObjectRef == nil { + klog.Warningf("Invalid matchExpression: %v", matchExpression) + continue + } + // find value from the object + objRef := &unstructured.Unstructured{} + objRef.SetAPIVersion(matchExpression.ObjectRef.APIVersion) + objRef.SetKind(matchExpression.ObjectRef.Kind) + objRef.SetName(matchExpression.ObjectRef.Name) + if err := r.Reader.Get(ctx, types.NamespacedName{ + Name: matchExpression.ObjectRef.Name, + Namespace: matchExpression.ObjectRef.Namespace, + }, objRef); err != nil { + klog.Warningf("Failed to get the object %v in match expressions: %v", matchExpression.ObjectRef, err) + continue + } + value, _ := util.SanitizeObjectString(matchExpression.Key, objRef.Object) + + switch matchExpression.Operator { + case operatorv1alpha1.OperatorIn: + if util.Contains(matchExpression.Values, value) { + return true + } + case operatorv1alpha1.OperatorNotIn: + if util.Contains(matchExpression.Values, value) { + return false + } else { + isMatch = true + } + case operatorv1alpha1.OperatorExists: + if value != "" { + return true + } + case operatorv1alpha1.OperatorDoesNotExist: + if value != "" { + return false + } else { + isMatch = true + } + default: + klog.Warningf("Invalid operator %s in match expressions: %v", matchExpression.Operator, matchExpression) + } + } + + return isMatch +} + +func (r *Reconciler) ServiceStatusIsReady(ctx context.Context, requestInstance *operatorv1alpha1.OperandRequest) (bool, error) { + requestedServicesSet := make(map[string]struct{}) + for _, req := range requestInstance.Spec.Requests { + registryKey := requestInstance.GetRegistryKey(req) + registryInstance, err := r.GetOperandRegistry(ctx, registryKey) + if err != nil { + klog.Errorf("Failed to get OperandRegistry %s, %v", registryKey, err) + return false, err + } + if registryInstance.Annotations != nil && registryInstance.Annotations[constant.StatusMonitoredServices] != "" { + monitoredServices := strings.Split(registryInstance.Annotations[constant.StatusMonitoredServices], ",") + for _, operand := range req.Operands { + if util.Contains(monitoredServices, operand.Name) { + requestedServicesSet[operand.Name] = struct{}{} + } + } + } + } + + if len(requestedServicesSet) == 0 { + klog.V(2).Infof("No services to be monitored for OperandRequest %s/%s", requestInstance.Namespace, requestInstance.Name) + return true, nil + } + + if len(requestInstance.Status.Services) == 0 { + klog.Infof("Waiting for status.services to be instantiated for OperandRequest %s/%s ...", requestInstance.Namespace, requestInstance.Name) + return false, nil + } + if len(requestedServicesSet) != len(requestInstance.Status.Services) { + klog.Infof("Waiting for status of all requested services to be instantiated for OperandRequest %s/%s ...", requestInstance.Namespace, requestInstance.Name) + return false, nil + } + + serviceStatus := true + // wait for the status of the requested services to be ready + for _, s := range requestInstance.Status.Services { + if _, ok := requestedServicesSet[s.OperatorName]; ok { + if s.Status != "Ready" { + klog.Infof("Waiting for status of service %s to be Ready for OperandRequest %s/%s ...", s.OperatorName, requestInstance.Namespace, requestInstance.Name) + serviceStatus = false + } + } + } + return serviceStatus, nil +} diff --git a/controllers/operandrequest/reconcile_operand_test.go b/controllers/operandrequest/reconcile_operand_test.go new file mode 100644 index 00000000..a88af6a0 --- /dev/null +++ b/controllers/operandrequest/reconcile_operand_test.go @@ -0,0 +1,287 @@ +// +// Copyright 2022 IBM Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package operandrequest + +import ( + "context" + "testing" + + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/pointer" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + operatorv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/v4/api/v1alpha1" + deploy "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/operator" +) + +type MockReader struct { + mock.Mock +} + +func (m *MockReader) Get(ctx context.Context, key client.ObjectKey, obj client.Object) error { + args := m.Called(ctx, key, obj) + return args.Error(0) +} +func (m *MockReader) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error { + args := m.Called(ctx, list, opts) + return args.Error(0) +} + +func TestSetOwnerReferences(t *testing.T) { + // Create a fake client + client := fake.NewClientBuilder().Build() + + // Create a mock reader + reader := &MockReader{} + + // Create a reconciler instance + r := &Reconciler{ + ODLMOperator: &deploy.ODLMOperator{ + Client: client, + Reader: reader, + }, + } + + // Create the controlled resource + controlledRes := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Pod", + "metadata": map[string]interface{}{ + "name": "test-pod", + "namespace": "test-namespace", + }, + }, + } + + // Create the owner references + ownerReferences := []operatorv1alpha1.OwnerReference{ + { + APIVersion: "v1", + Kind: "Deployment", + Name: "test-deployment", + Controller: pointer.BoolPtr(true), + }, + { + APIVersion: "v1", + Kind: "Service", + Name: "test-service", + Controller: pointer.BoolPtr(false), + }, + } + + // Mock the Get method of the reader + reader.On("Get", mock.Anything, types.NamespacedName{Name: "test-deployment", Namespace: "test-namespace"}, mock.AnythingOfType("*unstructured.Unstructured")).Return(nil) + reader.On("Get", mock.Anything, types.NamespacedName{Name: "test-service", Namespace: "test-namespace"}, mock.AnythingOfType("*unstructured.Unstructured")).Return(nil) + + // Call the setOwnerReferences function + err := r.setOwnerReferences(context.Background(), controlledRes, &ownerReferences) + + // Assert that there are no errors + assert.NoError(t, err) + + // Assert that the owner references are set correctly + expectedOwnerReferences := []metav1.OwnerReference{ + { + APIVersion: "v1", + Kind: "Deployment", + Name: "test-deployment", + Controller: pointer.BoolPtr(true), + BlockOwnerDeletion: pointer.BoolPtr(true), + }, + { + APIVersion: "v1", + Kind: "Service", + Name: "test-service", + }, + } + assert.Equal(t, expectedOwnerReferences, controlledRes.GetOwnerReferences()) +} +func TestFindMatchExpressions(t *testing.T) { + // Create a fake client + client := fake.NewClientBuilder().Build() + + // Create a mock reader + reader := &MockReader{} + + // Create a reconciler instance + r := &Reconciler{ + ODLMOperator: &deploy.ODLMOperator{ + Client: client, + Reader: reader, + }, + } + + // Define test cases + tests := []struct { + name string + matchExpressions []operatorv1alpha1.MatchExpression + expectedResult bool + mockGetError error + mockGetValue string + }{ + { + name: "Valid In operator", + matchExpressions: []operatorv1alpha1.MatchExpression{ + { + Key: ".spec.replicas", + Operator: operatorv1alpha1.OperatorIn, + Values: []string{"3", "4"}, + ObjectRef: &operatorv1alpha1.ObjectRef{ + APIVersion: "apps/v1", + Kind: "Deployment", + Name: "test-deployment", + Namespace: "test-namespace", + }, + }, + }, + expectedResult: true, + mockGetValue: "3", + }, + { + name: "Valid NotIn operator", + matchExpressions: []operatorv1alpha1.MatchExpression{ + { + Key: ".spec.replicas", + Operator: operatorv1alpha1.OperatorNotIn, + Values: []string{"4", "5"}, + ObjectRef: &operatorv1alpha1.ObjectRef{ + APIVersion: "apps/v1", + Kind: "Deployment", + Name: "test-deployment", + Namespace: "test-namespace", + }, + }, + }, + expectedResult: true, + mockGetValue: "3", + }, + { + name: "Valid Exists operator", + matchExpressions: []operatorv1alpha1.MatchExpression{ + { + Key: ".spec.replicas", + Operator: operatorv1alpha1.OperatorExists, + ObjectRef: &operatorv1alpha1.ObjectRef{ + APIVersion: "apps/v1", + Kind: "Deployment", + Name: "test-deployment", + Namespace: "test-namespace", + }, + }, + }, + expectedResult: true, + mockGetValue: "1", + }, + { + name: "Valid DoesNotExist operator", + matchExpressions: []operatorv1alpha1.MatchExpression{ + { + Key: ".spec.replicas", + Operator: operatorv1alpha1.OperatorDoesNotExist, + ObjectRef: &operatorv1alpha1.ObjectRef{ + APIVersion: "apps/v1", + Kind: "Deployment", + Name: "test-deployment", + Namespace: "test-namespace", + }, + }, + }, + expectedResult: false, + mockGetValue: "1", + }, + { + name: "Valid DoesNotExist operator with non-exist parh .spec.replica", + matchExpressions: []operatorv1alpha1.MatchExpression{ + { + Key: ".spec.replica", + Operator: operatorv1alpha1.OperatorDoesNotExist, + ObjectRef: &operatorv1alpha1.ObjectRef{ + APIVersion: "apps/v1", + Kind: "Deployment", + Name: "test-deployment", + Namespace: "test-namespace", + }, + }, + }, + expectedResult: true, + mockGetValue: "", + }, + { + name: "Invalid match expression", + matchExpressions: []operatorv1alpha1.MatchExpression{ + { + Key: "", + Operator: "In", + Values: []string{"3"}, + ObjectRef: &operatorv1alpha1.ObjectRef{ + APIVersion: "apps/v1", + Kind: "Deployment", + Name: "test-deployment", + Namespace: "test-namespace", + }, + }, + }, + expectedResult: false, + }, + { + name: "Failed to get object with path .spec.replica", + matchExpressions: []operatorv1alpha1.MatchExpression{ + { + Key: ".spec.replica", + Operator: operatorv1alpha1.OperatorIn, + Values: []string{"3"}, + ObjectRef: &operatorv1alpha1.ObjectRef{ + APIVersion: "apps/v1", + Kind: "Deployment", + Name: "test-deployment", + Namespace: "test-namespace", + }, + }, + }, + expectedResult: false, + mockGetError: errors.New("failed to get object"), + mockGetValue: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Mock the Get method of the reader + reader.On("Get", mock.Anything, types.NamespacedName{Name: "test-deployment", Namespace: "test-namespace"}, mock.AnythingOfType("*unstructured.Unstructured")).Return(tt.mockGetError).Run(func(args mock.Arguments) { + if tt.mockGetError == nil { + obj := args.Get(2).(*unstructured.Unstructured) + obj.Object["spec"] = map[string]interface{}{ + "replicas": tt.mockGetValue, + } + } + }) + + // Call the findMatchExpressions function + result := r.findMatchExpressions(context.Background(), tt.matchExpressions) + + // Assert the result + assert.Equal(t, tt.expectedResult, result) + }) + } +} diff --git a/controllers/operandrequest/reconcile_operator.go b/controllers/operandrequest/reconcile_operator.go index 6ed964db..3012ccc2 100644 --- a/controllers/operandrequest/reconcile_operator.go +++ b/controllers/operandrequest/reconcile_operator.go @@ -21,7 +21,6 @@ import ( "encoding/json" "fmt" "regexp" - "sort" "strings" "sync" "time" @@ -41,19 +40,22 @@ import ( "k8s.io/klog" "sigs.k8s.io/controller-runtime/pkg/client" - operatorv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/api/v1alpha1" - "github.com/IBM/operand-deployment-lifecycle-manager/controllers/constant" - "github.com/IBM/operand-deployment-lifecycle-manager/controllers/util" + operatorv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/v4/api/v1alpha1" + "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/constant" + "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/util" ) func (r *Reconciler) reconcileOperator(ctx context.Context, requestInstance *operatorv1alpha1.OperandRequest) error { klog.V(1).Infof("Reconciling Operators for OperandRequest: %s/%s", requestInstance.GetNamespace(), requestInstance.GetName()) // It is important to NOT pass the set directly into defer functions. // The arguments to the deferred function are evaluated when the defer executes - failedDeletedOperands := gset.NewSet() + remainingOperands := gset.NewSet() + for _, m := range requestInstance.Status.Members { + remainingOperands.Add(m.Name) + } // Update request status defer func() { - requestInstance.FreshMemberStatus(&failedDeletedOperands) + requestInstance.FreshMemberStatus(&remainingOperands) requestInstance.UpdateClusterPhase() }() @@ -67,7 +69,6 @@ func (r *Reconciler) reconcileOperator(ctx context.Context, requestInstance *ope } else { requestInstance.SetNoSuitableRegistryCondition(registryKey.String(), err.Error(), operatorv1alpha1.ResourceTypeOperandRegistry, corev1.ConditionTrue, &r.Mutex) } - klog.Errorf("Failed to get suitable OperandRegistry %s: %v", registryKey.String(), err) t := time.Now() formatted := fmt.Sprintf("%d-%02d-%02dT%02d:%02d:%02d", t.Year(), t.Month(), t.Day(), @@ -82,7 +83,7 @@ func (r *Reconciler) reconcileOperator(ctx context.Context, requestInstance *ope if patchErr := r.Patch(ctx, requestInstance, client.RawPatch(types.MergePatchType, mergePatch)); patchErr != nil { return utilerrors.NewAggregate([]error{err, patchErr}) } - return err + klog.Errorf("Failed to get suitable OperandRegistry %s: %v", registryKey.String(), err) } merr := &util.MultiErr{} @@ -122,24 +123,8 @@ func (r *Reconciler) reconcileOperator(ctx context.Context, requestInstance *ope } } - // TODO: update OperandRequest status before patching. Otherwise, the status will be lost. - // It is really corner case which would not cause any issue so far since the Status will finally be Running. - // Need to consider a more elegant way to preserve status instead of calling Client API like below - // requestInstance.FreshMemberStatus(&failedDeletedOperands) - - mergePatch, _ := json.Marshal(map[string]interface{}{ - "metadata": map[string]interface{}{ - "annotations": map[string]interface{}{ - constant.FindOperandRegistry: "false", - }, - }, - }) - if err := r.Patch(ctx, requestInstance, client.RawPatch(types.MergePatchType, mergePatch)); err != nil { - return err - } - // Delete specific operators - if err := r.absentOperatorsAndOperands(ctx, requestInstance, &failedDeletedOperands); err != nil { + if err := r.absentOperatorsAndOperands(ctx, requestInstance, &remainingOperands); err != nil { return err } klog.V(1).Infof("Finished reconciling Operators for OperandRequest: %s/%s", requestInstance.GetNamespace(), requestInstance.GetName()) @@ -149,9 +134,18 @@ func (r *Reconciler) reconcileOperator(ctx context.Context, requestInstance *ope func (r *Reconciler) reconcileSubscription(ctx context.Context, requestInstance *operatorv1alpha1.OperandRequest, registryInstance *operatorv1alpha1.OperandRegistry, operand operatorv1alpha1.Operand, registryKey types.NamespacedName, mu sync.Locker) error { // Check the requested Operand if exist in specific OperandRegistry - opt := registryInstance.GetOperator(operand.Name) + var opt *operatorv1alpha1.Operator + if registryInstance != nil { + var err error + opt, err = r.GetOperandFromRegistry(ctx, registryInstance, operand.Name) + if err != nil { + return err + } + } if opt == nil { - klog.V(1).Infof("Operator %s not found in the OperandRegistry %s/%s", operand.Name, registryInstance.Namespace, registryInstance.Name) + if registryInstance != nil { + klog.V(1).Infof("Operator %s not found in the OperandRegistry %s/%s", operand.Name, registryInstance.Namespace, registryInstance.Name) + } requestInstance.SetNotFoundOperatorFromRegistryCondition(operand.Name, operatorv1alpha1.ResourceTypeSub, corev1.ConditionTrue, mu) requestInstance.SetMemberStatus(operand.Name, operatorv1alpha1.OperatorNotFound, operatorv1alpha1.ServiceNotFound, mu) return nil @@ -164,29 +158,69 @@ func (r *Reconciler) reconcileSubscription(ctx context.Context, requestInstance // Check subscription if exist namespace := r.GetOperatorNamespace(opt.InstallMode, opt.Namespace) - sub, err := r.GetSubscription(ctx, opt.Name, namespace, opt.PackageName) + sub, err := r.GetSubscription(ctx, opt.Name, namespace, registryInstance.Namespace, opt.PackageName) - if err != nil { - if apierrors.IsNotFound(err) { - if opt.SupportStatus == operatorv1alpha1.MaintainedSupportStatus { - requestInstance.SetNoSuitableRegistryCondition(registryKey.String(), opt.Name+" is in maintenance status", operatorv1alpha1.ResourceTypeOperandRegistry, corev1.ConditionTrue, &r.Mutex) - requestInstance.SetMemberStatus(operand.Name, operatorv1alpha1.OperatorRunning, operatorv1alpha1.ServiceRunning, mu) - } else { - // Subscription does not exist, create a new one - if err = r.createSubscription(ctx, requestInstance, opt, registryKey); err != nil { - requestInstance.SetMemberStatus(opt.Name, operatorv1alpha1.OperatorFailed, "", mu) - return err - } - requestInstance.SetMemberStatus(opt.Name, operatorv1alpha1.OperatorInstalling, "", mu) + if opt.UserManaged { + klog.Infof("Skip installing operator %s because it is managed by user", opt.PackageName) + csvList, err := r.GetClusterServiceVersionListFromPackage(ctx, opt.PackageName, namespace) + if err != nil { + return errors.Wrapf(err, "failed to get CSV from package %s/%s", namespace, opt.PackageName) + } + if len(csvList) == 0 { + return errors.New("operator " + opt.Name + " is user managed, but no CSV exists, waiting...") + } + requestInstance.SetMemberStatus(opt.Name, operatorv1alpha1.OperatorUpdating, "", mu) + return nil + } + + if sub == nil && err == nil { + if opt.InstallMode == operatorv1alpha1.InstallModeNoop { + requestInstance.SetNoSuitableRegistryCondition(registryKey.String(), opt.Name+" is in maintenance status", operatorv1alpha1.ResourceTypeOperandRegistry, corev1.ConditionTrue, &r.Mutex) + requestInstance.SetMemberStatus(operand.Name, operatorv1alpha1.OperatorRunning, operatorv1alpha1.ServiceRunning, mu) + } else { + // Subscription does not exist, create a new one + if err = r.createSubscription(ctx, requestInstance, opt, registryKey); err != nil { + requestInstance.SetMemberStatus(opt.Name, operatorv1alpha1.OperatorFailed, "", mu) + return err } - return nil + requestInstance.SetMemberStatus(opt.Name, operatorv1alpha1.OperatorInstalling, "", mu) } + return nil + } else if err != nil { return err } // Subscription existing and managed by OperandRequest controller if _, ok := sub.Labels[constant.OpreqLabel]; ok { originalSub := sub.DeepCopy() + var isMatchedChannel bool + var isInScope bool + + if sub.Namespace == opt.Namespace { + isInScope = true + } else { + var nsAnnoSlice []string + namespaceReg, _ := regexp.Compile(`^(.*)\.(.*)\.(.*)\/operatorNamespace`) + for anno, ns := range sub.Annotations { + if namespaceReg.MatchString(anno) { + nsAnnoSlice = append(nsAnnoSlice, ns) + } + } + if len(nsAnnoSlice) != 0 && !util.Contains(nsAnnoSlice, sub.Namespace) { + + if r.checkUninstallLabel(sub) { + klog.V(1).Infof("Operator %s has label operator.ibm.com/opreq-do-not-uninstall. Skip the uninstall", opt.Name) + return nil + } + + if err = r.deleteSubscription(ctx, requestInstance, sub); err != nil { + requestInstance.SetMemberStatus(opt.Name, operatorv1alpha1.OperatorFailed, "", mu) + return err + } + requestInstance.SetMemberStatus(opt.Name, operatorv1alpha1.OperatorUpdating, "", mu) + return nil + } + } // add annotations to existing Subscriptions for upgrade case if sub.Annotations == nil { @@ -194,34 +228,50 @@ func (r *Reconciler) reconcileSubscription(ctx context.Context, requestInstance } sub.Annotations[registryKey.Namespace+"."+registryKey.Name+"/registry"] = "true" sub.Annotations[registryKey.Namespace+"."+registryKey.Name+"/config"] = "true" - sub.Annotations[requestInstance.Namespace+"."+requestInstance.Name+"/request"] = opt.Channel - - sub.Spec.CatalogSource = opt.SourceName - sub.Spec.CatalogSourceNamespace = opt.SourceNamespace - sub.Spec.Package = opt.PackageName - - // check request annotation in subscription, get all available channels - var semverlList []string - reg, _ := regexp.Compile(`^(.*)\.(.*)\/request`) - for anno, channel := range sub.Annotations { - if reg.MatchString(anno) && semver.IsValid(channel) { - semverlList = append(semverlList, channel) + sub.Annotations[requestInstance.Namespace+"."+requestInstance.Name+"."+operand.Name+"/request"] = opt.Channel + sub.Annotations[requestInstance.Namespace+"."+requestInstance.Name+"."+operand.Name+"/operatorNamespace"] = namespace + + if opt.InstallMode == operatorv1alpha1.InstallModeNoop { + isMatchedChannel = true + requestInstance.SetNoSuitableRegistryCondition(registryKey.String(), opt.Name+" is in maintenance status", operatorv1alpha1.ResourceTypeOperandRegistry, corev1.ConditionTrue, &r.Mutex) + requestInstance.SetMemberStatus(operand.Name, operatorv1alpha1.OperatorRunning, operatorv1alpha1.ServiceRunning, mu) + + // check if sub.Spec.Channel and opt.Channel are valid semantic version + // set annotation channel back to previous one if sub.Spec.Channel is lower than opt.Channel + // To avoid upgrade from one maintenance version to another maintenance version like from v3 to v3.23 + subChanel := util.FindSemantic(sub.Spec.Channel) + optChannel := util.FindSemantic(opt.Channel) + if semver.IsValid(subChanel) && semver.IsValid(optChannel) && semver.Compare(subChanel, optChannel) < 0 { + sub.Annotations[requestInstance.Namespace+"."+requestInstance.Name+"."+operand.Name+"/request"] = sub.Spec.Channel + } + } else if opt.SourceNamespace == "" || opt.SourceName == "" { + klog.Errorf("Failed to find catalogsource for operator %s with channel %s", opt.Name, opt.Channel) + requestInstance.SetMemberStatus(operand.Name, operatorv1alpha1.OperatorFailed, "", mu) + } else { + requestInstance.SetNotFoundOperatorFromRegistryCondition(operand.Name, operatorv1alpha1.ResourceTypeSub, corev1.ConditionFalse, mu) + + if minChannel := util.FindMinSemverFromAnnotations(sub.Annotations, sub.Spec.Channel); minChannel != "" { + sub.Spec.Channel = minChannel + } + + channels := []string{opt.Channel} + if channels = append(channels, opt.FallbackChannels...); util.Contains(channels, sub.Spec.Channel) { + isMatchedChannel = true + } + // update the spec iff channel in sub matches channel + if sub.Spec.Channel == opt.Channel { + sub.Spec.CatalogSource = opt.SourceName + sub.Spec.CatalogSourceNamespace = opt.SourceNamespace + sub.Spec.Package = opt.PackageName + + if opt.InstallPlanApproval != "" && sub.Spec.InstallPlanApproval != opt.InstallPlanApproval { + sub.Spec.InstallPlanApproval = opt.InstallPlanApproval + } + if opt.SubscriptionConfig != nil { + sub.Spec.Config = opt.SubscriptionConfig + } } - } - if len(semverlList) == 0 { - // channel is not valid semantic version - sub.Spec.Channel = opt.Channel - } else if !util.Contains(semverlList, sub.Spec.Channel) { - // upgrade channel to minimal version existing in annotation - sort.Sort(semver.ByVersion(semverlList)) - sub.Spec.Channel = semverlList[0] - } - if opt.InstallPlanApproval != "" && sub.Spec.InstallPlanApproval != opt.InstallPlanApproval { - sub.Spec.InstallPlanApproval = opt.InstallPlanApproval - } - if opt.SubscriptionConfig != nil { - sub.Spec.Config = opt.SubscriptionConfig } if compareSub(sub, originalSub) { if err = r.updateSubscription(ctx, requestInstance, sub); err != nil { @@ -230,6 +280,13 @@ func (r *Reconciler) reconcileSubscription(ctx context.Context, requestInstance } requestInstance.SetMemberStatus(opt.Name, operatorv1alpha1.OperatorUpdating, "", mu) } + + if !isMatchedChannel || !isInScope { + requestInstance.SetNoConflictOperatorCondition(operand.Name, operatorv1alpha1.ResourceTypeSub, corev1.ConditionFalse, mu) + requestInstance.SetMemberStatus(opt.Name, operatorv1alpha1.OperatorFailed, "", mu) + } else { + requestInstance.SetNoConflictOperatorCondition(operand.Name, operatorv1alpha1.ResourceTypeSub, corev1.ConditionTrue, mu) + } } else { // Subscription existing and not managed by OperandRequest controller klog.V(1).Infof("Subscription %s in namespace %s isn't created by ODLM. Ignore update/delete it.", sub.Name, sub.Namespace) @@ -298,21 +355,61 @@ func (r *Reconciler) updateSubscription(ctx context.Context, cr *operatorv1alpha return nil } -func (r *Reconciler) deleteSubscription(ctx context.Context, operandName string, requestInstance *operatorv1alpha1.OperandRequest, registryInstance *operatorv1alpha1.OperandRegistry, configInstance *operatorv1alpha1.OperandConfig) error { - op := registryInstance.GetOperator(operandName) +func (r *Reconciler) deleteSubscription(ctx context.Context, cr *operatorv1alpha1.OperandRequest, sub *olmv1alpha1.Subscription) error { + + klog.V(2).Infof("Deleting Subscription %s/%s ...", sub.Namespace, sub.Name) + + csvList, err := r.GetClusterServiceVersionList(ctx, sub) + // If can't get CSV, requeue the request + if err != nil { + return err + } + + if csvList != nil { + klog.Infof("Found %d ClusterServiceVersions for Subscription %s/%s", len(csvList), sub.Namespace, sub.Name) + for _, csv := range csvList { + klog.V(3).Info("Set Deleting Condition in the operandRequest") + cr.SetDeletingCondition(csv.Name, operatorv1alpha1.ResourceTypeCsv, corev1.ConditionTrue, &r.Mutex) + + klog.V(1).Infof("Deleting the ClusterServiceVersion, Namespace: %s, Name: %s", csv.Namespace, csv.Name) + if err := r.Delete(ctx, csv); err != nil { + cr.SetDeletingCondition(csv.Name, operatorv1alpha1.ResourceTypeCsv, corev1.ConditionFalse, &r.Mutex) + return err + } + } + } + + klog.V(2).Infof("Deleting the Subscription, Namespace: %s, Name: %s", sub.Namespace, sub.Name) + cr.SetDeletingCondition(sub.Name, operatorv1alpha1.ResourceTypeSub, corev1.ConditionTrue, &r.Mutex) + + if err := r.Delete(ctx, sub); err != nil { + if apierrors.IsNotFound(err) { + klog.Warningf("Subscription %s was not found in namespace %s", sub.Name, sub.Namespace) + } else { + cr.SetDeletingCondition(sub.Name, operatorv1alpha1.ResourceTypeSub, corev1.ConditionFalse, &r.Mutex) + return err + } + } + + klog.V(1).Infof("Subscription %s/%s is deleted", sub.Namespace, sub.Name) + return nil +} + +func (r *Reconciler) uninstallOperatorsAndOperands(ctx context.Context, operandName string, requestInstance *operatorv1alpha1.OperandRequest, registryInstance *operatorv1alpha1.OperandRegistry, configInstance *operatorv1alpha1.OperandConfig) error { + // No error handling for un-installation step in case Catalog has been deleted + op, _ := r.GetOperandFromRegistry(ctx, registryInstance, operandName) if op == nil { klog.Warningf("Operand %s not found", operandName) return nil } namespace := r.GetOperatorNamespace(op.InstallMode, op.Namespace) - sub, err := r.GetSubscription(ctx, operandName, namespace, op.PackageName) - originalsub := sub.DeepCopy() - if apierrors.IsNotFound(err) { - klog.V(3).Infof("There is no Subscription %s or %s in the namespace %s", operandName, op.PackageName, namespace) + sub, err := r.GetSubscription(ctx, operandName, namespace, registryInstance.Namespace, op.PackageName) + if sub == nil && err == nil { + klog.V(3).Infof("There is no Subscription %s or %s in the namespace %s and %s", operandName, op.PackageName, namespace, registryInstance.Namespace) return nil } else if err != nil { - klog.Errorf("Failed to get Subscription %s or %s in the namespace %s", operandName, op.PackageName, namespace) + klog.Errorf("Failed to get Subscription %s or %s in the namespace %s and %s", operandName, op.PackageName, namespace, registryInstance.Namespace) return err } @@ -323,89 +420,133 @@ func (r *Reconciler) deleteSubscription(ctx context.Context, operandName string, } if _, ok := sub.Labels[constant.OpreqLabel]; !ok { - // Subscription existing and not managed by OperandRequest controller - klog.V(2).Infof("Subscription %s in the namespace %s isn't created by ODLM", sub.Name, sub.Namespace) - return nil - } - - // remove request in annotation of subscription - reqName := requestInstance.ObjectMeta.Name - reqNs := requestInstance.ObjectMeta.Namespace - delete(sub.Annotations, reqNs+"."+reqName+"/request") - - var semverlList []string - var annoSlice []string - reg, _ := regexp.Compile(`^(.*)\.(.*)\/request`) - for anno, channel := range sub.Annotations { - if reg.MatchString(anno) { - annoSlice = append(annoSlice, anno) - if semver.IsValid(channel) { - semverlList = append(semverlList, channel) - } + if !op.UserManaged { + klog.V(2).Infof("Subscription %s in the namespace %s isn't created by ODLM and isn't user managed", sub.Name, sub.Namespace) + return nil } } - if len(annoSlice) != 0 { - // update channel to minmial version remaining in annotations - if len(semverlList) != 0 && !util.Contains(semverlList, sub.Spec.Channel) { - sort.Sort(semver.ByVersion(semverlList)) - sub.Spec.Channel = semverlList[0] - } - // remove the associated registry from annotation of subscription - if err := r.Patch(ctx, sub, client.MergeFrom(originalsub)); err != nil { - requestInstance.SetUpdatingCondition(sub.Name, operatorv1alpha1.ResourceTypeSub, corev1.ConditionFalse, &r.Mutex) + + uninstallOperator, uninstallOperand := checkSubAnnotationsForUninstall(requestInstance.ObjectMeta.Name, requestInstance.ObjectMeta.Namespace, op.Name, op.InstallMode, sub) + if !uninstallOperand && !uninstallOperator { + if err = r.updateSubscription(ctx, requestInstance, sub); err != nil { + requestInstance.SetMemberStatus(op.Name, operatorv1alpha1.OperatorFailed, "", &r.Mutex) return err } - klog.V(1).Infof("Did not delete Subscription %s/%s which is requested by other OperandRequests", sub.Namespace, sub.Name) + requestInstance.SetMemberStatus(op.Name, operatorv1alpha1.OperatorUpdating, "", &r.Mutex) + + klog.V(1).Infof("No deletion, subscription %s/%s and its operands are still requested by other OperandRequests", sub.Namespace, sub.Name) return nil } - csv, err := r.GetClusterServiceVersion(ctx, sub) - // If can't get CSV, requeue the request - if err != nil { + if csvList, err := r.GetClusterServiceVersionList(ctx, sub); err != nil { + // If can't get CSV, requeue the request return err + } else if csvList != nil { + klog.Infof("Found %d ClusterServiceVersions for Subscription %s/%s", len(csvList), sub.Namespace, sub.Name) + if uninstallOperand { + klog.V(2).Infof("Deleting all the Custom Resources for CSV, Namespace: %s, Name: %s", csvList[0].Namespace, csvList[0].Name) + if err := r.deleteAllCustomResource(ctx, csvList[0], requestInstance, configInstance, operandName, configInstance.Namespace); err != nil { + return err + } + klog.V(2).Infof("Deleting all the k8s Resources for CSV, Namespace: %s, Name: %s", csvList[0].Namespace, csvList[0].Name) + if err := r.deleteAllK8sResource(ctx, configInstance, operandName, configInstance.Namespace); err != nil { + return err + } + } + if uninstallOperator { + if r.checkUninstallLabel(sub) { + klog.V(1).Infof("Operator %s has label operator.ibm.com/opreq-do-not-uninstall. Skip the uninstall", op.Name) + return nil + } + + klog.V(3).Info("Set Deleting Condition in the operandRequest") + requestInstance.SetDeletingCondition(csvList[0].Name, operatorv1alpha1.ResourceTypeCsv, corev1.ConditionTrue, &r.Mutex) + + for _, csv := range csvList { + klog.V(1).Infof("Deleting the ClusterServiceVersion, Namespace: %s, Name: %s", csv.Namespace, csv.Name) + if err := r.Delete(ctx, csv); err != nil { + requestInstance.SetDeletingCondition(csv.Name, operatorv1alpha1.ResourceTypeCsv, corev1.ConditionFalse, &r.Mutex) + return errors.Wrapf(err, "failed to delete the ClusterServiceVersion %s/%s", csv.Namespace, csv.Name) + } + } + } } - if csv != nil { - klog.V(2).Infof("Deleting all the Custom Resources for CSV, Namespace: %s, Name: %s", csv.Namespace, csv.Name) - if err := r.deleteAllCustomResource(ctx, csv, requestInstance, configInstance, operandName, configInstance.Namespace); err != nil { - return err + if uninstallOperator { + klog.V(2).Infof("Deleting the Subscription, Namespace: %s, Name: %s", namespace, op.Name) + requestInstance.SetDeletingCondition(op.Name, operatorv1alpha1.ResourceTypeSub, corev1.ConditionTrue, &r.Mutex) + + if err := r.Delete(ctx, sub); err != nil { + if apierrors.IsNotFound(err) { + klog.Warningf("Subscription %s was not found in namespace %s", op.Name, namespace) + } else { + requestInstance.SetDeletingCondition(op.Name, operatorv1alpha1.ResourceTypeSub, corev1.ConditionFalse, &r.Mutex) + return errors.Wrap(err, "failed to delete subscription") + } } - klog.V(2).Infof("Deleting all the k8s Resources for CSV, Namespace: %s, Name: %s", csv.Namespace, csv.Name) - if err := r.deleteAllK8sResource(ctx, configInstance, operandName, configInstance.Namespace); err != nil { + + klog.V(1).Infof("Subscription %s/%s is deleted", namespace, op.Name) + } else { + if err = r.updateSubscription(ctx, requestInstance, sub); err != nil { + requestInstance.SetMemberStatus(op.Name, operatorv1alpha1.OperatorFailed, "", &r.Mutex) return err } - if r.checkUninstallLabel(sub) { - klog.V(1).Infof("Operator %s has label operator.ibm.com/opreq-do-not-uninstall. Skip the uninstall", op.Name) - return nil - } + requestInstance.SetMemberStatus(op.Name, operatorv1alpha1.OperatorUpdating, "", &r.Mutex) + klog.V(1).Infof("Subscription %s/%s is not deleted due to the annotation from OperandRequest", namespace, op.Name) + } - klog.V(3).Info("Set Deleting Condition in the operandRequest") - requestInstance.SetDeletingCondition(csv.Name, operatorv1alpha1.ResourceTypeCsv, corev1.ConditionTrue, &r.Mutex) + return nil +} - klog.V(1).Infof("Deleting the ClusterServiceVersion, Namespace: %s, Name: %s", csv.Namespace, csv.Name) - if err := r.Delete(ctx, csv); err != nil { - requestInstance.SetDeletingCondition(csv.Name, operatorv1alpha1.ResourceTypeCsv, corev1.ConditionFalse, &r.Mutex) - return errors.Wrap(err, "failed to delete the ClusterServiceVersion") - } +func (r *Reconciler) uninstallOperands(ctx context.Context, operandName string, requestInstance *operatorv1alpha1.OperandRequest, registryInstance *operatorv1alpha1.OperandRegistry, configInstance *operatorv1alpha1.OperandConfig) error { + // No error handling for un-installation step in case Catalog has been deleted + op, _ := r.GetOperandFromRegistry(ctx, registryInstance, operandName) + if op == nil { + klog.Warningf("Operand %s not found", operandName) + return nil } - klog.V(2).Infof("Deleting the Subscription, Namespace: %s, Name: %s", namespace, op.Name) - requestInstance.SetDeletingCondition(op.Name, operatorv1alpha1.ResourceTypeSub, corev1.ConditionTrue, &r.Mutex) + namespace := r.GetOperatorNamespace(op.InstallMode, op.Namespace) + uninstallOperand := false + operatorStatus, ok := registryInstance.Status.OperatorsStatus[op.Name] + if !ok { + return nil + } + if operatorStatus.ReconcileRequests == nil { + return nil + } + if len(operatorStatus.ReconcileRequests) > 1 { + return nil + } + if operatorStatus.ReconcileRequests[0].Name == requestInstance.Name { + uninstallOperand = true + } - if err := r.Delete(ctx, sub); err != nil { - if apierrors.IsNotFound(err) { - klog.Warningf("Subscription %s was not found in namespace %s", op.Name, namespace) - } else { - requestInstance.SetDeletingCondition(op.Name, operatorv1alpha1.ResourceTypeSub, corev1.ConditionFalse, &r.Mutex) - return errors.Wrap(err, "failed to delete subscription") + // get list reconcileRequests + // ignore the name which triggered reconcile + // if list is empty then uninstallOperand = true + + if csvList, err := r.GetClusterServiceVersionListFromPackage(ctx, op.PackageName, namespace); err != nil { + // If can't get CSV, requeue the request + return err + } else if csvList != nil { + klog.Infof("Found %d ClusterServiceVersions for package %s/%s", len(csvList), op.Name, namespace) + if uninstallOperand { + klog.V(2).Infof("Deleting all the Custom Resources for CSV, Namespace: %s, Name: %s", csvList[0].Namespace, csvList[0].Name) + if err := r.deleteAllCustomResource(ctx, csvList[0], requestInstance, configInstance, operandName, configInstance.Namespace); err != nil { + return err + } + klog.V(2).Infof("Deleting all the k8s Resources for CSV, Namespace: %s, Name: %s", csvList[0].Namespace, csvList[0].Name) + if err := r.deleteAllK8sResource(ctx, configInstance, operandName, configInstance.Namespace); err != nil { + return err + } } } - klog.V(1).Infof("Subscription %s/%s is deleted", namespace, op.Name) return nil } -func (r *Reconciler) absentOperatorsAndOperands(ctx context.Context, requestInstance *operatorv1alpha1.OperandRequest, failedDeletedOperands *gset.Set) error { +func (r *Reconciler) absentOperatorsAndOperands(ctx context.Context, requestInstance *operatorv1alpha1.OperandRequest, remainingOperands *gset.Set) error { needDeletedOperands := r.getNeedDeletedOperands(requestInstance) var ( @@ -435,12 +576,27 @@ func (r *Reconciler) absentOperatorsAndOperands(ctx context.Context, requestInst wg.Add(1) go func() { defer wg.Done() - if err := r.deleteSubscription(ctx, fmt.Sprintf("%v", o), requestInstance, registryInstance, configInstance); err != nil { - r.Mutex.Lock() - defer r.Mutex.Unlock() - merr.Add(err) - (*failedDeletedOperands).Add(o) + op, _ := r.GetOperandFromRegistry(ctx, registryInstance, fmt.Sprintf("%v", o)) + if op == nil { + klog.Warningf("Operand %s not found", fmt.Sprintf("%v", o)) + } + if op != nil && !op.UserManaged { + if err := r.uninstallOperatorsAndOperands(ctx, fmt.Sprintf("%v", o), requestInstance, registryInstance, configInstance); err != nil { + r.Mutex.Lock() + defer r.Mutex.Unlock() + merr.Add(err) + return // return here to avoid removing the operand from remainingOperands + } + } else { + if err := r.uninstallOperands(ctx, fmt.Sprintf("%v", o), requestInstance, registryInstance, configInstance); err != nil { + r.Mutex.Lock() + defer r.Mutex.Unlock() + merr.Add(err) + return // return here to avoid removing the operand from remainingOperands + } } + requestInstance.RemoveServiceStatus(fmt.Sprintf("%v", o), &r.Mutex) + (*remainingOperands).Remove(o) remainingOp.Remove(o) }() } @@ -457,7 +613,7 @@ func (r *Reconciler) absentOperatorsAndOperands(ctx context.Context, requestInst } func (r *Reconciler) getNeedDeletedOperands(requestInstance *operatorv1alpha1.OperandRequest) gset.Set { - klog.V(3).Info("Getting the operater need to be delete") + klog.V(3).Info("Getting the operator need to be delete") deployedOperands := gset.NewSet() for _, req := range requestInstance.Status.Members { deployedOperands.Add(req.Name) @@ -482,11 +638,6 @@ func (r *Reconciler) generateClusterObjects(o *operatorv1alpha1.Operator, regist labels := map[string]string{ constant.OpreqLabel: "true", } - annotations := map[string]string{ - registryKey.Namespace + "." + registryKey.Name + "/registry": "true", - registryKey.Namespace + "." + registryKey.Name + "/config": "true", - requestKey.Namespace + "." + requestKey.Name + "/request": o.Channel, - } klog.V(3).Info("Generating Namespace: ", o.Namespace) // Namespace Object @@ -509,10 +660,17 @@ func (r *Reconciler) generateClusterObjects(o *operatorv1alpha1.Operator, regist // The namespace is 'openshift-operators' when installMode is cluster namespace := r.GetOperatorNamespace(o.InstallMode, o.Namespace) + annotations := map[string]string{ + registryKey.Namespace + "." + registryKey.Name + "/registry": "true", + registryKey.Namespace + "." + registryKey.Name + "/config": "true", + requestKey.Namespace + "." + requestKey.Name + "." + o.Name + "/request": o.Channel, + requestKey.Namespace + "." + requestKey.Name + "." + o.Name + "/operatorNamespace": namespace, + } + // Subscription Object sub := &olmv1alpha1.Subscription{ ObjectMeta: metav1.ObjectMeta{ - Name: o.Name, + Name: o.PackageName, Namespace: namespace, Labels: labels, Annotations: annotations, @@ -528,7 +686,7 @@ func (r *Reconciler) generateClusterObjects(o *operatorv1alpha1.Operator, regist }, } sub.SetGroupVersionKind(schema.GroupVersionKind{Group: olmv1alpha1.SchemeGroupVersion.Group, Kind: "Subscription", Version: olmv1alpha1.SchemeGroupVersion.Version}) - klog.V(3).Info("Generating Subscription: ", o.Name, " in the Namespace: ", namespace) + klog.V(3).Info("Generating Subscription: ", o.PackageName, " in the Namespace: ", namespace) co.subscription = sub return co } @@ -569,3 +727,56 @@ func CheckSingletonServices(operator string) bool { singletonServices := []string{"ibm-cert-manager-operator", "ibm-licensing-operator"} return util.Contains(singletonServices, operator) } + +// checkSubAnnotationsForUninstall checks the annotations of a Subscription object +// to determine whether the operator and operand should be uninstalled. +// It takes the name of the OperandRequest, the namespace of the OperandRequest, +// the name of the operator, and a pointer to the Subscription object as input. +// It returns two boolean values: uninstallOperator and uninstallOperand. +// If uninstallOperator is true, it means the operator should be uninstalled. +// If uninstallOperand is true, it means the operand should be uninstalled. +func checkSubAnnotationsForUninstall(reqName, reqNs, opName, installMode string, sub *olmv1alpha1.Subscription) (bool, bool) { + uninstallOperator := true + uninstallOperand := true + + delete(sub.Annotations, reqNs+"."+reqName+"."+opName+"/request") + delete(sub.Annotations, reqNs+"."+reqName+"."+opName+"/operatorNamespace") + + var opreqNsSlice []string + var operatorNameSlice []string + namespaceReg, _ := regexp.Compile(`^(.*)\.(.*)\.(.*)\/operatorNamespace`) + channelReg, _ := regexp.Compile(`^(.*)\.(.*)\.(.*)\/request`) + + for key, value := range sub.Annotations { + if namespaceReg.MatchString(key) { + opreqNsSlice = append(opreqNsSlice, value) + } + + if channelReg.MatchString(key) { + // Extract the operator name from the key + keyParts := strings.Split(key, "/") + annoPrefix := strings.Split(keyParts[0], ".") + operatorNameSlice = append(operatorNameSlice, annoPrefix[len(annoPrefix)-1]) + } + } + + // If one of remaining /operatorNamespace annotations' values is the same as subscription's namespace, + // the operator should NOT be uninstalled. + if util.Contains(opreqNsSlice, sub.Namespace) { + uninstallOperator = false + } + + if value, ok := sub.Labels[constant.OpreqLabel]; !ok || value != "true" { + uninstallOperator = false + } + + // When one of following conditions are met, the operand will NOT be uninstalled: + // 1. operator is not uninstalled AND intallMode is no-op. + // 2. operator is uninstalled AND at least one other /operatorNamespace annotation exists. + // 2. remaining /request annotation's values contain the same operator name + if (!uninstallOperator && installMode == operatorv1alpha1.InstallModeNoop) || (uninstallOperator && len(opreqNsSlice) != 0) || util.Contains(operatorNameSlice, opName) { + uninstallOperand = false + } + + return uninstallOperator, uninstallOperand +} diff --git a/controllers/operandrequest/reconcile_operator_test.go b/controllers/operandrequest/reconcile_operator_test.go new file mode 100644 index 00000000..3232d703 --- /dev/null +++ b/controllers/operandrequest/reconcile_operator_test.go @@ -0,0 +1,329 @@ +// +// Copyright 2022 IBM Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package operandrequest + +import ( + "testing" + + olmv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + + operatorv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/v4/api/v1alpha1" + "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/constant" +) + +func TestGenerateClusterObjects(t *testing.T) { + r := &Reconciler{} + + o := &operatorv1alpha1.Operator{ + Namespace: "test-namespace", + TargetNamespaces: []string{"target-namespace"}, + InstallMode: "namespace", + Channel: "test-channel", + PackageName: "test-package", + SourceName: "test-source", + SourceNamespace: "test-source-namespace", + InstallPlanApproval: "Automatic", + StartingCSV: "test-csv", + SubscriptionConfig: &olmv1alpha1.SubscriptionConfig{ + Env: []corev1.EnvVar{ + { + Name: "key", + Value: "value", + }, + }, + }, + } + + registryKey := types.NamespacedName{ + Namespace: "test-registry-namespace", + Name: "test-registry-name", + } + requestKey := types.NamespacedName{ + Namespace: "test-namespace", + Name: "test-opreq", + } + + co := r.generateClusterObjects(o, registryKey, requestKey) + + assert.NotNil(t, co.namespace) + assert.Equal(t, "Namespace", co.namespace.Kind) + assert.Equal(t, "v1", co.namespace.APIVersion) + assert.Equal(t, "test-namespace", co.namespace.Name) + assert.Equal(t, map[string]string{"operator.ibm.com/opreq-control": "true"}, co.namespace.Labels) + + assert.NotNil(t, co.operatorGroup) + assert.Equal(t, "OperatorGroup", co.operatorGroup.Kind) + assert.Equal(t, "test-namespace", co.operatorGroup.Namespace) + assert.Equal(t, []string{"target-namespace"}, co.operatorGroup.Spec.TargetNamespaces) + + assert.NotNil(t, co.subscription) + assert.Equal(t, "Subscription", co.subscription.Kind) + assert.Equal(t, "test-namespace", co.subscription.Namespace) + assert.Equal(t, "test-channel", co.subscription.Spec.Channel) + assert.Equal(t, "test-package", co.subscription.Spec.Package) + assert.Equal(t, "test-source", co.subscription.Spec.CatalogSource) + assert.Equal(t, "test-source-namespace", co.subscription.Spec.CatalogSourceNamespace) + assert.Equal(t, olmv1alpha1.Approval("Automatic"), co.subscription.Spec.InstallPlanApproval) + assert.Equal(t, "test-csv", co.subscription.Spec.StartingCSV) + assert.NotNil(t, co.subscription.Spec.Config) + assert.Equal(t, "value", co.subscription.Spec.Config.Env[0].Value) +} + +func TestCheckSubAnnotationsForUninstall(t *testing.T) { + + reqNameA := "common-service" + reqNsOld := "ibm-common-services" + opNameV3 := "ibm-iam" + opChannelV3 := "v3" + + reqNameB := "other-request" + reqNsNew := "other-namespace" + opNameV4 := "ibm-im" + opChannelV4 := "v4.0" + + reqNameC := "common-service-postgresql" + opNameC := "common-service-postgresql" + opChannelC := "stable-v1.22" + + reqNameD := "edb-keycloak" + opNameD := "edb-keycloak" + opChannelD := "stable" + + // The annotation key has prefix: ../* + + // When following conditions are met, the operator should be uninstalled: + // If all remaining /operatorNamespace annotations' values are not the same as subscription's namespace, the operator should be uninstalled. + + // When one of following conditions are met, the operand will NOT be uninstalled: + // 1. operator is not uninstalled AND intallMode is no-op. + // 2. operator is uninstalled AND at least one other /operatorNamespace annotation exists. + // 2. remaining /request annotation's values contain the same operator name + + // Test case 1: uninstallOperator is true, uninstallOperand is true for uninstalling operator with v3 no-op installMode + // The operator and operand should be uninstalled because no remaining annotation is left. + sub := &olmv1alpha1.Subscription{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: reqNsOld, + Annotations: map[string]string{ + reqNsOld + "." + reqNameA + "." + opNameV3 + "/request": opChannelV3, + reqNsOld + "." + reqNameA + "." + opNameV3 + "/operatorNamespace": reqNsOld, + }, + Labels: map[string]string{ + constant.OpreqLabel: "true", + }, + }, + } + + uninstallOperator, uninstallOperand := checkSubAnnotationsForUninstall(reqNameA, reqNsOld, opNameV3, operatorv1alpha1.InstallModeNoop, sub) + + assert.True(t, uninstallOperator) + assert.True(t, uninstallOperand) + + assert.NotContains(t, sub.Annotations, reqNsOld+"."+reqNameA+"."+opNameV3+"/request") + assert.NotContains(t, sub.Annotations, reqNsOld+"."+reqNameA+"."+opNameV3+"/operatorNamespace") + + // Test case 2: uninstallOperator is true, uninstallOperand is true for uninstalling operator with general installMode + // The operator and operand should be uninstalled because no remaining annotation is left. + sub = &olmv1alpha1.Subscription{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: reqNsOld, + Annotations: map[string]string{ + reqNsOld + "." + reqNameA + "." + opNameV4 + "/request": opChannelV4, + reqNsOld + "." + reqNameA + "." + opNameV4 + "/operatorNamespace": reqNsOld, + }, + Labels: map[string]string{ + constant.OpreqLabel: "true", + }, + }, + } + + uninstallOperator, uninstallOperand = checkSubAnnotationsForUninstall(reqNameA, reqNsOld, opNameV4, operatorv1alpha1.InstallModeNamespace, sub) + + assert.True(t, uninstallOperator) + assert.True(t, uninstallOperand) + + assert.NotContains(t, sub.Annotations, reqNsOld+"."+reqNameA+"."+opNameV4+"/request") + assert.NotContains(t, sub.Annotations, reqNsOld+"."+reqNameA+"."+opNameV4+"/operatorNamespace") + + // Test case 3: uninstallOperator is true, uninstallOperand is false for all namespace migration + // where operator is uninstalled and will be reinstalled in new namespace and operand is not for old operator with v3 no-op installMode + sub = &olmv1alpha1.Subscription{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: reqNsOld, + Annotations: map[string]string{ + reqNsOld + "." + reqNameA + "." + opNameV3 + "/request": opChannelV3, + reqNsOld + "." + reqNameA + "." + opNameV3 + "/operatorNamespace": reqNsOld, + reqNsNew + "." + reqNameB + "." + opNameV4 + "/request": opChannelV4, + reqNsNew + "." + reqNameB + "." + opNameV4 + "/operatorNamespace": reqNsNew, + }, + Labels: map[string]string{ + constant.OpreqLabel: "true", + }, + }, + } + + uninstallOperator, uninstallOperand = checkSubAnnotationsForUninstall(reqNameA, reqNsOld, opNameV3, operatorv1alpha1.InstallModeNoop, sub) + + assert.True(t, uninstallOperator) + assert.False(t, uninstallOperand) + + assert.NotContains(t, sub.Annotations, reqNsOld+"."+reqNameA+"."+opNameV3+"/request") + assert.NotContains(t, sub.Annotations, reqNsOld+"."+reqNameA+"."+opNameV3+"/operatorNamespace") + assert.Contains(t, sub.Annotations, reqNsNew+"."+reqNameB+"."+opNameV4+"/request") + assert.Contains(t, sub.Annotations, reqNsNew+"."+reqNameB+"."+opNameV4+"/operatorNamespace") + + // Test case 4: uninstallOperator is true, uninstallOperand is false for all namespace migration + // where operator is uninstalled and will be reinstalled in new namespace and operand is not for operator with general installMode + sub = &olmv1alpha1.Subscription{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: reqNsOld, + Annotations: map[string]string{ + reqNsOld + "." + reqNameA + "." + opNameV3 + "/request": opChannelV3, + reqNsOld + "." + reqNameA + "." + opNameV3 + "/operatorNamespace": reqNsOld, + reqNsNew + "." + reqNameB + "." + opNameV4 + "/request": opChannelV4, + reqNsNew + "." + reqNameB + "." + opNameV4 + "/operatorNamespace": reqNsNew, + }, + Labels: map[string]string{ + constant.OpreqLabel: "true", + }, + }, + } + + uninstallOperator, uninstallOperand = checkSubAnnotationsForUninstall(reqNameA, reqNsOld, opNameV3, operatorv1alpha1.InstallModeNamespace, sub) + + assert.True(t, uninstallOperator) + assert.False(t, uninstallOperand) + + assert.NotContains(t, sub.Annotations, reqNsOld+"."+reqNameA+"."+opNameV3+"/request") + assert.NotContains(t, sub.Annotations, reqNsOld+"."+reqNameA+"."+opNameV3+"/operatorNamespace") + assert.Contains(t, sub.Annotations, reqNsNew+"."+reqNameB+"."+opNameV4+"/request") + assert.Contains(t, sub.Annotations, reqNsNew+"."+reqNameB+"."+opNameV4+"/operatorNamespace") + + // Test case 5: uninstallOperator is false, uninstallOperand is true for removing operand only for specific operandrequest + // The operator should not be uninstalled because the remaining request is requesting operator is in the same namespace as the Subscription. + // The operand should be uninstalled because this operand is NOT noop InstallMode And no other OperandRequest is requesting the same operand. + // For example, no OperandRequest is requesting common-service-postgresql. + sub = &olmv1alpha1.Subscription{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: reqNsOld, + Annotations: map[string]string{ + reqNsOld + "." + reqNameC + "." + opNameC + "/request": opChannelC, + reqNsOld + "." + reqNameC + "." + opNameC + "/operatorNamespace": reqNsOld, + reqNsOld + "." + reqNameD + "." + opNameD + "/request": opChannelD, + reqNsOld + "." + reqNameD + "." + opNameD + "/operatorNamespace": reqNsOld, + }, + Labels: map[string]string{ + constant.OpreqLabel: "true", + }, + }, + } + + uninstallOperator, uninstallOperand = checkSubAnnotationsForUninstall(reqNameC, reqNsOld, opNameC, operatorv1alpha1.InstallModeNamespace, sub) + + assert.False(t, uninstallOperator) + assert.True(t, uninstallOperand) + + assert.NotContains(t, sub.Annotations, reqNsOld+"."+reqNameC+"."+opNameC+"/request") + assert.NotContains(t, sub.Annotations, reqNsOld+"."+reqNameC+"."+opNameC+"/operatorNamespace") + assert.Contains(t, sub.Annotations, reqNsOld+"."+reqNameD+"."+opNameD+"/request") + assert.Contains(t, sub.Annotations, reqNsOld+"."+reqNameD+"."+opNameD+"/operatorNamespace") + + // Test case 6: uninstallOperator is false, uninstallOperand is false for removing one no-op operand only for upgrade scenario + // The operator should not be uninstalled because the remaining request is requesting operator is in the same namespace as the Subscription. + // The operand should NOT be uninstalled because this operand is noop InstallMode And other OperandRequest is requesting the newer version operand. + // For example, IAM -> IM in-place upgrade scenario. + sub = &olmv1alpha1.Subscription{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: reqNsOld, + Annotations: map[string]string{ + reqNsOld + "." + reqNameA + "." + opNameV3 + "/request": opChannelV3, + reqNsOld + "." + reqNameA + "." + opNameV3 + "/operatorNamespace": reqNsOld, + reqNsOld + "." + reqNameB + "." + opNameV4 + "/request": opChannelV4, + reqNsOld + "." + reqNameB + "." + opNameV4 + "/operatorNamespace": reqNsOld, + }, + Labels: map[string]string{ + constant.OpreqLabel: "true", + }, + }, + } + + uninstallOperator, uninstallOperand = checkSubAnnotationsForUninstall(reqNameA, reqNsOld, opNameV3, operatorv1alpha1.InstallModeNoop, sub) + + assert.False(t, uninstallOperator) + assert.False(t, uninstallOperand) + + assert.NotContains(t, sub.Annotations, reqNsOld+"."+reqNameA+"."+opNameV3+"/request") + assert.NotContains(t, sub.Annotations, reqNsOld+"."+reqNameA+"."+opNameV3+"/operatorNamespace") + assert.Contains(t, sub.Annotations, reqNsOld+"."+reqNameB+"."+opNameV4+"/request") + assert.Contains(t, sub.Annotations, reqNsOld+"."+reqNameB+"."+opNameV4+"/operatorNamespace") + + // Test case 7: uninstallOperator is false, uninstallOperand is false + // The operator and operand should not be uninstalled because at least one different OperandRequest requesting same operator + // For example, two OperandRequests are requesting IM. + sub = &olmv1alpha1.Subscription{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: reqNsNew, + Annotations: map[string]string{ + reqNsNew + "." + reqNameA + "." + opNameV4 + "/request": opChannelV4, + reqNsNew + "." + reqNameA + "." + opNameV4 + "/operatorNamespace": reqNsNew, + reqNsNew + "." + reqNameB + "." + opNameV4 + "/request": opChannelV4, + reqNsNew + "." + reqNameB + "." + opNameV4 + "/operatorNamespace": reqNsNew, + }, + Labels: map[string]string{ + constant.OpreqLabel: "true", + }, + }, + } + + uninstallOperator, uninstallOperand = checkSubAnnotationsForUninstall(reqNameA, reqNsNew, opNameV4, operatorv1alpha1.InstallModeNoop, sub) + + assert.False(t, uninstallOperator) + assert.False(t, uninstallOperand) + + assert.NotContains(t, sub.Annotations, reqNsNew+"."+reqNameA+"."+opNameV4+"/request") + assert.NotContains(t, sub.Annotations, reqNsNew+"."+reqNameA+"."+opNameV4+"/operatorNamespace") + assert.Contains(t, sub.Annotations, reqNsNew+"."+reqNameB+"."+opNameV4+"/request") + assert.Contains(t, sub.Annotations, reqNsNew+"."+reqNameB+"."+opNameV4+"/operatorNamespace") + + // Test case 8: uninstallOperator is false, uninstallOperand is true for operator with general installMode + // The operator should be NOT uninstalled because operator is not been managed by ODLM. + // The operand should be uninstalled because no other OperandRequest is requesting the same operand. + sub = &olmv1alpha1.Subscription{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: reqNsOld, + Annotations: map[string]string{ + reqNsOld + "." + reqNameA + "." + opNameV4 + "/request": opChannelV4, + reqNsOld + "." + reqNameA + "." + opNameV4 + "/operatorNamespace": reqNsOld, + }, + Labels: map[string]string{ + constant.OpreqLabel: "false", + }, + }, + } + + uninstallOperator, uninstallOperand = checkSubAnnotationsForUninstall(reqNameA, reqNsOld, opNameV4, operatorv1alpha1.InstallModeNamespace, sub) + + assert.False(t, uninstallOperator) + assert.True(t, uninstallOperand) + + assert.NotContains(t, sub.Annotations, reqNsOld+"."+reqNameA+"."+opNameV4+"/request") + assert.NotContains(t, sub.Annotations, reqNsOld+"."+reqNameA+"."+opNameV4+"/operatorNamespace") +} diff --git a/controllers/operandrequestnoolm/operandrequestnoolm_controller.go b/controllers/operandrequestnoolm/operandrequestnoolm_controller.go new file mode 100644 index 00000000..377cc10d --- /dev/null +++ b/controllers/operandrequestnoolm/operandrequestnoolm_controller.go @@ -0,0 +1,403 @@ +// +// Copyright 2022 IBM Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package operandrequestnoolm + +import ( + "context" + "fmt" + "reflect" + "strings" + "sync" + "time" + + gset "github.com/deckarep/golang-set" + "github.com/pkg/errors" + authorizationv1 "k8s.io/api/authorization/v1" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + utilerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/klog" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/source" + + operatorv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/v4/api/v1alpha1" + "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/constant" + deploy "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/operator" +) + +// Reconciler reconciles a OperandRequest object +type Reconciler struct { + *deploy.ODLMOperator + StepSize int + Mutex sync.Mutex +} +type clusterObjects struct { + namespace *corev1.Namespace + configmap *corev1.ConfigMap +} + +//+kubebuilder:rbac:groups=operator.ibm.com,resources=certmanagers;auditloggings,verbs=get;delete +//+kubebuilder:rbac:groups=operators.coreos.com,resources=catalogsources,verbs=get +//+kubebuilder:rbac:groups=apiextensions.k8s.io,resources=customresourcedefinitions,verbs=get + +//+kubebuilder:rbac:groups=*,namespace="placeholder",resources=*,verbs=create;delete;get;list;patch;update;watch +//+kubebuilder:rbac:groups=operator.ibm.com,namespace="placeholder",resources=operandrequests;operandrequests/status;operandrequests/finalizers,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups="",namespace="placeholder",resources=configmaps;secrets;services;namespaces,verbs=create;delete;get;list;patch;update;watch +//+kubebuilder:rbac:groups=route.openshift.io,namespace="placeholder",resources=routes,verbs=create;delete;get;list;patch;update;watch +//+kubebuilder:rbac:groups=operators.coreos.com,namespace="placeholder",resources=operatorgroups;installplans,verbs=create;delete;get;list;patch;update;watch +//+kubebuilder:rbac:groups=k8s.keycloak.org,namespace="placeholder",resources=keycloaks;keycloakrealmimports,verbs=create;delete;get;list;patch;update;watch +//+kubebuilder:rbac:groups=packages.operators.coreos.com,namespace="placeholder",resources=packagemanifests,verbs=get;list;patch;update;watch +//+kubebuilder:rbac:groups=operators.coreos.com,namespace="placeholder",resources=clusterserviceversions;subscriptions,verbs=create;delete;get;list;patch;update;watch + +// Reconcile reads that state of the cluster for a OperandRequest object and makes changes based on the state read +// and what is in the OperandRequest.Spec +// Note: +// The Controller will requeue the Request to be processed again if the returned error is non-nil or +// Result.Requeue is true, otherwise upon completion it will remove the work from the queue. +func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Result, reconcileErr error) { + // Fetch the OperandRequest instance + requestInstance := &operatorv1alpha1.OperandRequest{} + if err := r.Client.Get(ctx, req.NamespacedName, requestInstance); err != nil { + // Error reading the object - requeue the request. + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + originalInstance := requestInstance.DeepCopy() + + // Always attempt to patch the status after each reconciliation. + defer func() { + // get the latest instance from the server and check if the status has changed + existingInstance := &operatorv1alpha1.OperandRequest{} + if err := r.Client.Get(ctx, req.NamespacedName, existingInstance); err != nil && !apierrors.IsNotFound(err) { + // Error reading the latest object - requeue the request. + reconcileErr = utilerrors.NewAggregate([]error{reconcileErr, fmt.Errorf("error while get latest OperandRequest.Status from server: %v", err)}) + } + + if reflect.DeepEqual(existingInstance.Status, requestInstance.Status) { + return + } + + // Update requestInstance's resource version to avoid conflicts + requestInstance.ResourceVersion = existingInstance.ResourceVersion + if err := r.Client.Status().Patch(ctx, requestInstance, client.MergeFrom(existingInstance)); err != nil && !apierrors.IsNotFound(err) { + reconcileErr = utilerrors.NewAggregate([]error{reconcileErr, fmt.Errorf("error while patching OperandRequest.Status: %v", err)}) + } + if reconcileErr != nil { + klog.Errorf("failed to patch status for OperandRequest %s: %v", req.NamespacedName.String(), reconcileErr) + } + }() + + // Get start to delete the OperandRequest + // Remove finalizer when DeletionTimestamp none zero + if !requestInstance.ObjectMeta.DeletionTimestamp.IsZero() { + //Checkfinalizers calls the delete function, not necessarily subscription based + //Check and clean up the operands + err := r.checkFinalizer(ctx, requestInstance) + if err != nil { + klog.Errorf("failed to clean up the operands for OperandRequest %s: %v", req.NamespacedName.String(), err) + return ctrl.Result{}, err + } + + originalReq := requestInstance.DeepCopy() + // Update finalizer to allow delete CR + if requestInstance.RemoveFinalizer() { + err := r.Patch(ctx, requestInstance, client.MergeFrom(originalReq)) + if err != nil { + klog.Errorf("failed to remove finalizer for OperandRequest %s: %v", req.NamespacedName.String(), err) + return ctrl.Result{}, client.IgnoreNotFound(err) + } + } + return ctrl.Result{}, nil + } + + // Check if operator has the update permission to update OperandRequest + hasPermission := r.checkPermission(ctx, req) + if !hasPermission { + klog.Warningf("No permission to update OperandRequest") + return ctrl.Result{RequeueAfter: 3 * time.Second}, nil + } + + klog.V(1).Infof("Reconciling OperandRequest: %s", req.NamespacedName) + // Update labels for the request + if requestInstance.UpdateLabels() { + if err := r.Patch(ctx, requestInstance, client.MergeFrom(originalInstance)); err != nil { + klog.Errorf("failed to update the labels for OperandRequest %s: %v", req.NamespacedName.String(), err) + return ctrl.Result{}, err + } + } + + // Initialize the status for OperandRequest instance + if !requestInstance.InitRequestStatus(&r.Mutex) { + return ctrl.Result{Requeue: true}, nil + } + + // Add finalizer to the instance + if isAdded, err := r.addFinalizer(ctx, requestInstance); err != nil { + klog.Errorf("failed to add finalizer for OperandRequest %s: %v", req.NamespacedName.String(), err) + return ctrl.Result{}, err + } else if !isAdded { + return ctrl.Result{Requeue: true}, err + } + + //Many of the cleanup functions are run through reconcileoperator as of 1.31.25 + //technically nothing is done to the operator deployment as of this writing (1.31.25) + // Reconcile Operators + if err := r.reconcileOperator(ctx, requestInstance); err != nil { + klog.Errorf("failed to reconcile Operators for OperandRequest %s: %v", req.NamespacedName.String(), err) + return ctrl.Result{}, err + } + + // Reconcile Operands + if merr := r.reconcileOperand(ctx, requestInstance); len(merr.Errors) != 0 { + klog.Errorf("failed to reconcile Operands for OperandRequest %s: %v", req.NamespacedName.String(), merr) + return ctrl.Result{}, merr + } + + //TODO update this block to check deployments and their operands instead + // Check if all csv deploy succeed + if requestInstance.Status.Phase != operatorv1alpha1.ClusterPhaseRunning { + klog.V(2).Info("Waiting for all operators and operands to be deployed successfully ...") + return ctrl.Result{RequeueAfter: constant.DefaultRequeueDuration}, nil + } + + //check if status.services is present (if a relevant service was requested), requeue again if service is not ready yet + if isReady, err := r.ServiceStatusIsReady(ctx, requestInstance); !isReady || err != nil { + return ctrl.Result{RequeueAfter: constant.DefaultRequeueDuration}, nil + } + + klog.V(1).Infof("Finished reconciling OperandRequest: %s", req.NamespacedName) + return ctrl.Result{RequeueAfter: constant.DefaultSyncPeriod}, reconcileErr +} + +func (r *Reconciler) checkPermission(ctx context.Context, req ctrl.Request) bool { + // Check update permission + if !r.checkUpdateAuth(ctx, req.Namespace, "operator.ibm.com", "operandrequests") { + return false + } + if !r.checkUpdateAuth(ctx, req.Namespace, "operator.ibm.com", "operandrequests/status") { + return false + } + return true +} + +// Check if operator has permission to update OperandRequest +func (r *Reconciler) checkUpdateAuth(ctx context.Context, namespace, group, resource string) bool { + sar := &authorizationv1.SelfSubjectAccessReview{ + Spec: authorizationv1.SelfSubjectAccessReviewSpec{ + ResourceAttributes: &authorizationv1.ResourceAttributes{ + Namespace: namespace, + Verb: "update", + Group: group, + Resource: resource, + }, + }, + } + + if err := r.Create(ctx, sar); err != nil { + klog.Errorf("Failed to check operator update permission: %v", err) + return false + } + + klog.V(3).Infof("Operator update permission in namespace %s, Allowed: %t, Denied: %t, Reason: %s", namespace, sar.Status.Allowed, sar.Status.Denied, sar.Status.Reason) + return sar.Status.Allowed +} + +func (r *Reconciler) addFinalizer(ctx context.Context, cr *operatorv1alpha1.OperandRequest) (bool, error) { + if cr.GetDeletionTimestamp() == nil { + originalReq := cr.DeepCopy() + added := cr.EnsureFinalizer() + if added { + // Add finalizer to OperandRequest instance + err := r.Patch(ctx, cr, client.MergeFrom(originalReq)) + if err != nil { + return false, errors.Wrapf(err, "failed to update the OperandRequest %s/%s", cr.Namespace, cr.Name) + } + return false, nil + } + } + return true, nil +} + +func (r *Reconciler) checkFinalizer(ctx context.Context, requestInstance *operatorv1alpha1.OperandRequest) error { + klog.V(1).Infof("Deleting OperandRequest %s in the namespace %s", requestInstance.Name, requestInstance.Namespace) + remainingOperands := gset.NewSet() + for _, m := range requestInstance.Status.Members { + klog.V(3).Infof("Operand %s added to deletion list", m.Name) + remainingOperands.Add(m.Name) + } + // Delete all the operands and configmaps that created by current request + if err := r.absentOperatorsAndOperands(ctx, requestInstance, &remainingOperands); err != nil { + return err + } + return nil +} + +func (r *Reconciler) getRegistryToRequestMapper() handler.MapFunc { + ctx := context.Background() + return func(object client.Object) []ctrl.Request { + requestList, _ := r.ListOperandRequestsByRegistry(ctx, types.NamespacedName{Namespace: object.GetNamespace(), Name: object.GetName()}) + + requests := []ctrl.Request{} + for _, request := range requestList { + namespaceName := types.NamespacedName{Name: request.Name, Namespace: request.Namespace} + req := ctrl.Request{NamespacedName: namespaceName} + requests = append(requests, req) + } + return requests + } +} + +func (r *Reconciler) getConfigToRequestMapper() handler.MapFunc { + ctx := context.Background() + return func(object client.Object) []ctrl.Request { + requestList, _ := r.ListOperandRequestsByConfig(ctx, types.NamespacedName{Namespace: object.GetNamespace(), Name: object.GetName()}) + + requests := []ctrl.Request{} + for _, request := range requestList { + namespaceName := types.NamespacedName{Name: request.Name, Namespace: request.Namespace} + req := ctrl.Request{NamespacedName: namespaceName} + requests = append(requests, req) + } + return requests + } +} + +func (r *Reconciler) getReferenceToRequestMapper() handler.MapFunc { + ctx := context.Background() + return func(object client.Object) []ctrl.Request { + annotations := object.GetAnnotations() + if annotations == nil { + return []ctrl.Request{} + } + odlmReference, ok := annotations[constant.ODLMReferenceAnnotation] + if !ok { + return []ctrl.Request{} + } + odlmReferenceSlices := strings.Split(odlmReference, ".") + if len(odlmReferenceSlices) != 3 { + return []ctrl.Request{} + } + + var requestList []operatorv1alpha1.OperandRequest + if odlmReferenceSlices[0] == "OperandConfig" { + requestList, _ = r.ListOperandRequestsByConfig(ctx, types.NamespacedName{Namespace: odlmReferenceSlices[1], Name: odlmReferenceSlices[2]}) + } else if odlmReferenceSlices[0] == "OperandRegistry" { + requestList, _ = r.ListOperandRequestsByRegistry(ctx, types.NamespacedName{Namespace: odlmReferenceSlices[1], Name: odlmReferenceSlices[2]}) + } else { + return []ctrl.Request{} + } + + requests := []ctrl.Request{} + for _, request := range requestList { + namespaceName := types.NamespacedName{Name: request.Name, Namespace: request.Namespace} + req := ctrl.Request{NamespacedName: namespaceName} + requests = append(requests, req) + } + return requests + } +} + +// SetupWithManager adds OperandRequest controller to the manager. +func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error { + options := controller.Options{ + MaxConcurrentReconciles: r.MaxConcurrentReconciles, // Set the desired value for max concurrent reconciles. + } + ReferencePredicates := predicate.Funcs{ + CreateFunc: func(e event.CreateEvent) bool { + labels := e.Object.GetLabels() + // only return true when both conditions are met at the same time: + // 1. label contain key "constant.ODLMWatchedLabel" and value is true + // 2. label does not contain key "constant.OpbiTypeLabel" with value "copy" + if labels != nil { + if labelValue, ok := labels[constant.ODLMWatchedLabel]; ok && labelValue == "true" { + if labelValue, ok := labels[constant.OpbiTypeLabel]; ok && labelValue == "copy" { + return false + } + return true + } + } + return false + }, + UpdateFunc: func(e event.UpdateEvent) bool { + labels := e.ObjectNew.GetLabels() + if labels != nil { + if labelValue, ok := labels[constant.ODLMWatchedLabel]; ok && labelValue == "true" { + if labelValue, ok := labels[constant.OpbiTypeLabel]; ok && labelValue == "copy" { + return false + } + return true + } + } + return false + }, + DeleteFunc: func(e event.DeleteEvent) bool { + return true + }, + } + return ctrl.NewControllerManagedBy(mgr). + WithOptions(options). + For(&operatorv1alpha1.OperandRequest{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})). + Watches(&source.Kind{Type: &corev1.ConfigMap{}}, handler.EnqueueRequestsFromMapFunc(r.getReferenceToRequestMapper()), builder.WithPredicates(predicate.Funcs{ + UpdateFunc: func(e event.UpdateEvent) bool { + oldObject := e.ObjectOld.(*corev1.ConfigMap) + newObject := e.ObjectNew.(*corev1.ConfigMap) + if oldObject.Labels != nil && oldObject.Labels[constant.OpreqLabel] == "true" { + // statusToggle := (oldObject.Status.InstalledCSV != "" && newObject.Status.InstalledCSV != "" && oldObject.Status.InstalledCSV != newObject.Status.InstalledCSV) + metadataToggle := !reflect.DeepEqual(oldObject.Annotations, newObject.Annotations) + // return statusToggle || metadataToggle + return metadataToggle + } + return false + }, + DeleteFunc: func(e event.DeleteEvent) bool { + // Evaluates to false if the object has been confirmed deleted. + return false + }, + })). + Watches(&source.Kind{Type: &operatorv1alpha1.OperandRegistry{}}, handler.EnqueueRequestsFromMapFunc(r.getRegistryToRequestMapper()), builder.WithPredicates(predicate.Funcs{ + UpdateFunc: func(e event.UpdateEvent) bool { + oldObject := e.ObjectOld.(*operatorv1alpha1.OperandRegistry) + newObject := e.ObjectNew.(*operatorv1alpha1.OperandRegistry) + return !reflect.DeepEqual(oldObject.Spec, newObject.Spec) || !reflect.DeepEqual(oldObject.GetAnnotations(), newObject.GetAnnotations()) + }, + DeleteFunc: func(e event.DeleteEvent) bool { + // Evaluates to false if the object has been confirmed deleted. + return !e.DeleteStateUnknown + }, + })). + Watches(&source.Kind{Type: &operatorv1alpha1.OperandConfig{}}, handler.EnqueueRequestsFromMapFunc(r.getConfigToRequestMapper()), builder.WithPredicates(predicate.Funcs{ + DeleteFunc: func(e event.DeleteEvent) bool { + // Evaluates to false if the object has been confirmed deleted. + return !e.DeleteStateUnknown + }, + UpdateFunc: func(e event.UpdateEvent) bool { + oldObject := e.ObjectOld.(*operatorv1alpha1.OperandConfig) + newObject := e.ObjectNew.(*operatorv1alpha1.OperandConfig) + return !reflect.DeepEqual(oldObject.Spec, newObject.Spec) + }, + })). + Watches(&source.Kind{Type: &corev1.ConfigMap{}}, handler.EnqueueRequestsFromMapFunc(r.getReferenceToRequestMapper()), builder.WithPredicates(ReferencePredicates)). + Watches(&source.Kind{Type: &corev1.Secret{}}, handler.EnqueueRequestsFromMapFunc(r.getReferenceToRequestMapper()), builder.WithPredicates(ReferencePredicates)). + Complete(r) +} diff --git a/controllers/operandrequestnoolm/reconcile_operand.go b/controllers/operandrequestnoolm/reconcile_operand.go new file mode 100644 index 00000000..65844b01 --- /dev/null +++ b/controllers/operandrequestnoolm/reconcile_operand.go @@ -0,0 +1,1591 @@ +// +// Copyright 2022 IBM Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package operandrequestnoolm + +import ( + "context" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "strconv" + "strings" + "sync" + "time" + + "github.com/pkg/errors" + appsv1 "k8s.io/api/apps/v1" + authorizationv1 "k8s.io/api/authorization/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/equality" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + runtime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/discovery" + "k8s.io/klog" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + operatorv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/v4/api/v1alpha1" + constant "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/constant" + util "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/util" +) + +func (r *Reconciler) reconcileOperand(ctx context.Context, requestInstance *operatorv1alpha1.OperandRequest) *util.MultiErr { + klog.V(1).Infof("Reconciling Operands for OperandRequest: %s/%s", requestInstance.GetNamespace(), requestInstance.GetName()) + // Update request status + defer func() { + requestInstance.UpdateClusterPhase() + }() + + merr := &util.MultiErr{} + if err := r.checkCustomResource(ctx, requestInstance); err != nil { + merr.Add(err) + return merr + } + for _, req := range requestInstance.Spec.Requests { + registryKey := requestInstance.GetRegistryKey(req) + registryInstance, err := r.GetOperandRegistry(ctx, registryKey) + if err != nil { + merr.Add(errors.Wrapf(err, "failed to get the OperandRegistry %s", registryKey.String())) + continue + } + + for i, operand := range req.Operands { + + opdRegistry, err := r.GetOperandFromRegistry(ctx, registryInstance, operand.Name) + if err != nil { + klog.Warningf("Failed to get the Operand %s from the OperandRegistry %s", operand.Name, registryKey.String()) + merr.Add(errors.Wrapf(err, "failed to get the Operand %s from the OperandRegistry %s", operand.Name, registryKey.String())) + continue + } else if opdRegistry == nil { + klog.Warningf("Cannot find %s in the OperandRegistry instance %s in the namespace %s ", operand.Name, req.Registry, req.RegistryNamespace) + requestInstance.SetMemberStatus(operand.Name, operatorv1alpha1.OperatorNotFound, operatorv1alpha1.ServiceNotFound, &r.Mutex) + continue + } + + // Set no-op operator to Running status + if opdRegistry.InstallMode == operatorv1alpha1.InstallModeNoop { + requestInstance.SetNoSuitableRegistryCondition(registryKey.String(), opdRegistry.Name+" is in maintenance status", operatorv1alpha1.ResourceTypeOperandRegistry, corev1.ConditionTrue, &r.Mutex) + requestInstance.SetMemberStatus(operand.Name, operatorv1alpha1.OperatorRunning, operatorv1alpha1.ServiceRunning, &r.Mutex) + continue + } + + operatorName := opdRegistry.Name + + klog.V(1).Info("Looking for deployment for the operator: ", operatorName) + + // Looking for the CSV + namespace := r.GetOperatorNamespace(opdRegistry.InstallMode, opdRegistry.Namespace) + + var deployment *appsv1.Deployment + + deploymentList, err := r.GetDeploymentListFromPackage(ctx, opdRegistry.PackageName, opdRegistry.Namespace) + if err != nil { + merr.Add(err) + requestInstance.SetMemberStatus(operand.Name, operatorv1alpha1.OperatorFailed, "", &r.Mutex) + continue + } + deployment = deploymentList[0] + + if deployment == nil { + klog.Warningf("Deployment for %s in the namespace %s is not ready yet, retry", operatorName, namespace) + requestInstance.SetMemberStatus(operand.Name, operatorv1alpha1.OperatorInstalling, "", &r.Mutex) + continue + } + + // TODO determine if we need to reimagine this function for noolm use, do we want odlm cleaning up extra deployments? How should ODLM know the difference between "good" and "bad" deployments? + //otherwise get rid of it + // if err := r.DeleteRedundantCSV(ctx, csv.Name, csv.Namespace, registryKey.Namespace, opdRegistry.PackageName); err != nil { + // merr.Add(errors.Wrapf(err, "failed to delete the redundant ClusterServiceVersion %s in the namespace %s", csv.Name, csv.Namespace)) + // requestInstance.SetMemberStatus(operand.Name, operatorv1alpha1.OperatorFailed, "", &r.Mutex) + // continue + // } + + klog.V(3).Info("Generating customresource base on Deployment: ", deployment.GetName()) + requestInstance.SetMemberStatus(operand.Name, operatorv1alpha1.OperatorRunning, "", &r.Mutex) + + // Merge and Generate CR + if operand.Kind == "" { + configInstance, err := r.GetOperandConfig(ctx, registryKey) + if err == nil { + // Check the requested Service Config if exist in specific OperandConfig + opdConfig := configInstance.GetService(operand.Name) + if opdConfig == nil && !opdRegistry.UserManaged { + klog.V(2).Infof("There is no service: %s from the OperandConfig instance: %s/%s, Skip reconciling Operands", operand.Name, registryKey.Namespace, req.Registry) + continue + } + err = r.reconcileCRwithConfig(ctx, opdConfig, configInstance.Name, configInstance.Namespace, deployment, requestInstance, operand.Name, deployment.Namespace, &r.Mutex) + if err != nil { + merr.Add(err) + requestInstance.SetMemberStatus(operand.Name, "", operatorv1alpha1.ServiceFailed, &r.Mutex) + continue + } + } else if apierrors.IsNotFound(err) { + klog.Infof("Not Found OperandConfig: %s/%s", operand.Name, err) + } else { + merr.Add(errors.Wrapf(err, "failed to get the OperandConfig %s", registryKey.String())) + continue + } + + } else { + err = r.reconcileCRwithRequest(ctx, requestInstance, operand, types.NamespacedName{Name: requestInstance.Name, Namespace: requestInstance.Namespace}, i, deployment.Namespace, &r.Mutex) + if err != nil { + merr.Add(err) + requestInstance.SetMemberStatus(operand.Name, "", operatorv1alpha1.ServiceFailed, &r.Mutex) + continue + } + } + requestInstance.SetMemberStatus(operand.Name, "", operatorv1alpha1.ServiceRunning, &r.Mutex) + } + } + if len(merr.Errors) != 0 { + return merr + } + klog.V(1).Infof("Finished reconciling Operands for OperandRequest: %s/%s", requestInstance.GetNamespace(), requestInstance.GetName()) + return &util.MultiErr{} +} + +// reconcileCRwithConfig merge and create custom resource base on OperandConfig and deployment alm-examples +func (r *Reconciler) reconcileCRwithConfig(ctx context.Context, service *operatorv1alpha1.ConfigService, opConfigName, opConfigNs string, deployment *appsv1.Deployment, requestInstance *operatorv1alpha1.OperandRequest, operandName string, operatorNamespace string, mu sync.Locker) error { + merr := &util.MultiErr{} + + // Create k8s resources required by service + if service.Resources != nil { + // Get the chunk size + var chunkSize int + if r.StepSize > 0 { + chunkSize = r.StepSize + } else { + chunkSize = 1 + } + var wg sync.WaitGroup + semaphore := make(chan struct{}, chunkSize) + + for i := range service.Resources { + wg.Add(1) + semaphore <- struct{}{} + go func(res operatorv1alpha1.ConfigResource) { + defer wg.Done() + defer func() { <-semaphore }() // release semaphore + err := r.reconcileK8sResourceWithRetries(ctx, res, service.Name, opConfigName, opConfigNs) + if err != nil { + merr.Add(err) + } + }(service.Resources[i]) + } + + wg.Wait() + + if len(merr.Errors) != 0 { + return merr + } + } + + almExamples := deployment.GetAnnotations()["alm-examples"] + + // Convert CR template string to slice + var almExampleList []interface{} + err := json.Unmarshal([]byte(almExamples), &almExampleList) + if err != nil { + return errors.Wrapf(err, "failed to convert alm-examples in the Subscription %s/%s to slice", opConfigNs, service.Name) + } + + foundMap := make(map[string]bool) + for cr := range service.Spec { + foundMap[cr] = false + } + + serviceObject, err := util.ObjectToNewUnstructured(service) + if err != nil { + klog.Errorf("Failed to convert OperandConfig service object %s to unstructured.Unstructured object", service.Name) + return err + } + + if err := r.ParseValueReferenceInObject(ctx, "spec", serviceObject.Object["spec"], serviceObject.Object, "OperandConfig", opConfigName, opConfigNs); err != nil { + klog.Errorf("Failed to parse value reference for service %s: %v", service.Name, err) + return err + } + // cover unstructured.Unstructured object to original OperandConfig object + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(serviceObject.Object, service); err != nil { + klog.Errorf("Failed to convert unstructured.Unstructured object to service object %s", service.Name) + return err + } + + // Merge OperandConfig and ClusterServiceVersion alm-examples + for _, almExample := range almExampleList { + // Create an unstructured object for CR and check its value + var crFromALM unstructured.Unstructured + crFromALM.Object = almExample.(map[string]interface{}) + + name := crFromALM.GetName() + spec := crFromALM.Object["spec"] + if spec == nil { + continue + } + + err := r.Client.Get(ctx, types.NamespacedName{ + Name: name, + Namespace: opConfigNs, + }, &crFromALM) + + foundInConfig := false + for cr := range service.Spec { + if strings.EqualFold(crFromALM.GetKind(), cr) { + foundMap[cr] = true + foundInConfig = true + } + } + + if !foundInConfig { + klog.Warningf("%v in the alm-example doesn't exist in the OperandConfig for %v", crFromALM.GetKind(), deployment.GetName()) + continue + } + + if err != nil && !apierrors.IsNotFound(err) { + merr.Add(errors.Wrapf(err, "failed to get the custom resource %s/%s", opConfigNs, name)) + continue + } else if apierrors.IsNotFound(err) { + // Create Custom Resource + if err := r.compareConfigandExample(ctx, crFromALM, service, opConfigNs); err != nil { + merr.Add(err) + continue + } + } else { + if r.CheckLabel(crFromALM, map[string]string{constant.OpreqLabel: "true"}) { + // Update or Delete Custom Resource + if err := r.existingCustomResource(ctx, crFromALM, spec.(map[string]interface{}), service, opConfigNs); err != nil { + merr.Add(err) + continue + } + statusFromCR, err := r.getOperandStatus(crFromALM) + if err != nil { + return err + } + serviceKind := crFromALM.GetKind() + if serviceKind != "OperandRequest" && statusFromCR.ObjectName != "" { + var resources []operatorv1alpha1.OperandStatus + resources = append(resources, statusFromCR) + serviceStatus := newServiceStatus(operandName, operatorNamespace, resources) + if seterr := requestInstance.SetServiceStatus(ctx, serviceStatus, r.Client, mu); seterr != nil { + return seterr + } + } + } else { + klog.V(2).Info("Skip the custom resource not created by ODLM") + } + } + } + if len(merr.Errors) != 0 { + return merr + } + + for cr, found := range foundMap { + if !found { + klog.Warningf("Custom resource %v doesn't exist in the alm-example of %v", cr, deployment.GetName()) + } + } + + return nil +} + +// reconcileCRwithRequest merge and create custom resource base on OperandRequest and CSV alm-examples +func (r *Reconciler) reconcileCRwithRequest(ctx context.Context, requestInstance *operatorv1alpha1.OperandRequest, operand operatorv1alpha1.Operand, requestKey types.NamespacedName, index int, operatorNamespace string, mu sync.Locker) error { + merr := &util.MultiErr{} + + // Create an unstructured object for CR and check its value + var crFromRequest unstructured.Unstructured + + if operand.APIVersion == "" { + return fmt.Errorf("the APIVersion of operand is empty for operator %s", operand.Name) + } + + if operand.Kind == "" { + return fmt.Errorf("the Kind of operand is empty for operator %s", operand.Name) + } + + var name string + if operand.InstanceName == "" { + crInfo := sha256.Sum256([]byte(operand.APIVersion + operand.Kind + strconv.Itoa(index))) + name = requestKey.Name + "-" + hex.EncodeToString(crInfo[:7]) + } else { + name = operand.InstanceName + } + + crFromRequest.SetName(name) + crFromRequest.SetNamespace(requestKey.Namespace) + crFromRequest.SetAPIVersion(operand.APIVersion) + crFromRequest.SetKind(operand.Kind) + // Set the OperandRequest as the owner of the CR from request + if err := controllerutil.SetOwnerReference(requestInstance, &crFromRequest, r.Scheme); err != nil { + merr.Add(errors.Wrapf(err, "failed to set ownerReference for custom resource %s/%s", requestKey.Namespace, name)) + } + + err := r.Client.Get(ctx, types.NamespacedName{ + Name: name, + Namespace: requestKey.Namespace, + }, &crFromRequest) + + if err != nil && !apierrors.IsNotFound(err) { + merr.Add(errors.Wrapf(err, "failed to get custom resource %s/%s", requestKey.Namespace, name)) + } else if apierrors.IsNotFound(err) { + // Create Custom resource + if err := r.createCustomResource(ctx, crFromRequest, requestKey.Namespace, operand.Kind, operand.Spec.Raw); err != nil { + merr.Add(err) + } + requestInstance.SetMemberCRStatus(operand.Name, name, operand.Kind, operand.APIVersion, &r.Mutex) + } else { + if r.CheckLabel(crFromRequest, map[string]string{constant.OpreqLabel: "true"}) { + // Update or Delete Custom resource + klog.V(3).Info("Found existing custom resource: " + operand.Kind) + if err := r.updateCustomResource(ctx, crFromRequest, requestKey.Namespace, operand.Kind, operand.Spec.Raw, map[string]interface{}{}, requestInstance); err != nil { + return err + } + statusFromCR, err := r.getOperandStatus(crFromRequest) + if err != nil { + return err + } + if operand.Kind != "OperandRequest" && statusFromCR.ObjectName != "" { + var resources []operatorv1alpha1.OperandStatus + resources = append(resources, statusFromCR) + serviceStatus := newServiceStatus(operand.Name, operatorNamespace, resources) + seterr := requestInstance.SetServiceStatus(ctx, serviceStatus, r.Client, mu) + if seterr != nil { + return seterr + } + } + } else { + klog.V(2).Info("Skip the custom resource not created by ODLM") + } + } + + if len(merr.Errors) != 0 { + return merr + } + return nil +} + +func (r *Reconciler) getOperandStatus(existingCR unstructured.Unstructured) (operatorv1alpha1.OperandStatus, error) { + var emptyStatus operatorv1alpha1.OperandStatus + byteStatus, err := json.Marshal(existingCR.Object["status"]) + if err != nil { + klog.Error(err) + return emptyStatus, err + } + var rawStatus map[string]interface{} + err = json.Unmarshal(byteStatus, &rawStatus) + if err != nil { + klog.Error(err) + return emptyStatus, err + } + var serviceStatus operatorv1alpha1.OperandStatus + byteService, err := json.Marshal(rawStatus["service"]) + if err != nil { + klog.Error(err) + return emptyStatus, err + } + err = json.Unmarshal(byteService, &serviceStatus) + if err != nil { + klog.Error(err) + return emptyStatus, err + } + return serviceStatus, nil +} + +func newServiceStatus(operatorName string, namespace string, resources []operatorv1alpha1.OperandStatus) operatorv1alpha1.ServiceStatus { + var serviceSpec operatorv1alpha1.ServiceStatus + serviceSpec.OperatorName = operatorName + serviceSpec.Namespace = namespace + // serviceSpec.Type = "Ready" //should this be something more specific? Like operandNameReady? + status := "Ready" + for i := range resources { + if resources[i].Status == "NotReady" { + status = "NotReady" + break + } else { + for j := range resources[i].ManagedResources { + if resources[i].ManagedResources[j].Status == "NotReady" { + status = "NotReady" + break + } + } + } + } + serviceSpec.Status = status //TODO logic to determine readiness + serviceSpec.Resources = resources + return serviceSpec +} + +func (r *Reconciler) reconcileK8sResourceWithRetries(ctx context.Context, res operatorv1alpha1.ConfigResource, serviceName, opConfigName, opConfigNs string) error { + var err error + for i := 0; i < int(constant.DefaultCRRetryNumber); i++ { + err = r.reconcileK8sResource(ctx, res, serviceName, opConfigName, opConfigNs) + if err == nil { + return nil + } + klog.Errorf("Failed to reconcile k8s resource -- Kind: %s, NamespacedName: %s/%s with error: %v", res.Kind, res.Namespace, res.Name, err) + if i < int(constant.DefaultCRRetryNumber)-1 { + waitTime := time.Duration((1 << i) * 4 * int(time.Second)) + klog.Warningf("Retry reconcile k8s resource -- Kind: %s, NamespacedName: %s/%s after waiting %v", res.Kind, res.Namespace, res.Name, waitTime) + time.Sleep(waitTime) + } + } + return err +} + +func (r *Reconciler) reconcileK8sResource(ctx context.Context, res operatorv1alpha1.ConfigResource, serviceName, opConfigName, opConfigNs string) error { + if res.APIVersion == "" { + return fmt.Errorf("the APIVersion of k8s resource is empty for operator %s", serviceName) + } + + if res.Kind == "" { + return fmt.Errorf("the Kind of k8s resource is empty for operator %s", serviceName) + } + if res.Name == "" { + return fmt.Errorf("the Name of k8s resource is empty for operator %s", serviceName) + } + var k8sResNs string + if res.Namespace == "" { + k8sResNs = opConfigNs + } else { + k8sResNs = res.Namespace + } + + resObject, err := util.ObjectToNewUnstructured(&res) + if err != nil { + klog.Errorf("Failed to convert %s %s/%s object to unstructured.Unstructured object", res.Kind, k8sResNs, res.Name) + return err + } + + if err := r.ParseValueReferenceInObject(ctx, "data", resObject.Object["data"], resObject.Object, "OperandConfig", opConfigName, opConfigNs); err != nil { + klog.Errorf("Failed to parse value reference in resource %s/%s: %v", k8sResNs, res.Name, err) + return err + } + // cover unstructured.Unstructured object to original OperandConfig object + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(resObject.Object, &res); err != nil { + klog.Errorf("Failed to convert unstructured.Unstructured object to %s %s/%s object", res.Kind, k8sResNs, res.Name) + return err + } + + var k8sRes unstructured.Unstructured + k8sRes.SetAPIVersion(res.APIVersion) + k8sRes.SetKind(res.Kind) + k8sRes.SetName(res.Name) + k8sRes.SetNamespace(k8sResNs) + + verbs := []string{"create", "delete", "get", "update"} + if r.checkResAuth(ctx, verbs, k8sRes) { + err := r.Client.Get(ctx, types.NamespacedName{ + Name: res.Name, + Namespace: k8sResNs, + }, &k8sRes) + + if err != nil && !apierrors.IsNotFound(err) { + return errors.Wrapf(err, "failed to get k8s resource %s/%s", k8sResNs, res.Name) + } else if apierrors.IsNotFound(err) { + if err := r.createK8sResource(ctx, k8sRes, res.Data, res.Labels, res.Annotations, &res.OwnerReferences, &res.OptionalFields); err != nil { + return err + } + } else { + if res.Force { + // Update k8s resource + klog.V(3).Info("Found existing k8s resource: " + res.Name) + if err := r.updateK8sResource(ctx, k8sRes, res.Data, res.Labels, res.Annotations, &res.OwnerReferences, &res.OptionalFields); err != nil { + return err + } + } else { + klog.V(2).Infof("Skip the k8s resource %s/%s which is not created by ODLM", res.Kind, res.Name) + } + } + } else { + klog.Infof("ODLM doesn't have enough permission to reconcile k8s resource -- Kind: %s, NamespacedName: %s/%s", res.Kind, k8sResNs, res.Name) + } + return nil +} + +// deleteAllCustomResource remove custom resource base on OperandConfig and CSV alm-examples +func (r *Reconciler) deleteAllCustomResource(ctx context.Context, deployment *appsv1.Deployment, requestInstance *operatorv1alpha1.OperandRequest, csc *operatorv1alpha1.OperandConfig, operandName, namespace string) error { + + customeResourceMap := make(map[string]operatorv1alpha1.OperandCRMember) + for _, member := range requestInstance.Status.Members { + if len(member.OperandCRList) != 0 { + if member.Name == operandName { + for _, cr := range member.OperandCRList { + customeResourceMap[member.Name+"/"+cr.Kind+"/"+cr.Name] = cr + } + } + } + } + + merr := &util.MultiErr{} + var ( + wg sync.WaitGroup + ) + for index, opdMember := range customeResourceMap { + crShouldBeDeleted := unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": opdMember.APIVersion, + "kind": opdMember.Kind, + "metadata": map[string]interface{}{ + "name": opdMember.Name, + }, + }, + } + + var ( + operatorName = strings.Split(index, "/")[0] + opdMember = opdMember + ) + + wg.Add(1) + go func() { + defer wg.Done() + if err := r.deleteCustomResource(ctx, crShouldBeDeleted, requestInstance.Namespace); err != nil { + r.Mutex.Lock() + defer r.Mutex.Unlock() + merr.Add(err) + return + } + requestInstance.RemoveMemberCRStatus(operatorName, opdMember.Name, opdMember.Kind, &r.Mutex) + }() + } + wg.Wait() + + if len(merr.Errors) != 0 { + klog.Errorf("Failed to delete custom resource from OperandRequest for operator %s", operandName) + return merr + } + + service := csc.GetService(operandName) + if service == nil { + return nil + } + almExamples := deployment.GetAnnotations()["alm-examples"] + klog.V(2).Info("Delete all the custom resource from Subscription ", service.Name) + + // Create a slice for crTemplates + var almExamplesRaw []interface{} + + // Convert CR template string to slice + err := json.Unmarshal([]byte(almExamples), &almExamplesRaw) + if err != nil { + return errors.Wrapf(err, "failed to convert alm-examples in the Subscription %s to slice", service.Name) + } + + // Merge OperandConfig and ClusterServiceVersion alm-examples + for _, crFromALM := range almExamplesRaw { + + // Get CR from the alm-example + var crTemplate unstructured.Unstructured + crTemplate.Object = crFromALM.(map[string]interface{}) + crTemplate.SetNamespace(namespace) + name := crTemplate.GetName() + // Get the kind of CR + kind := crTemplate.GetKind() + // Delete the CR + for crdName := range service.Spec { + + // Compare the name of OperandConfig and CRD name + if strings.EqualFold(kind, crdName) { + if r.checkResAuth(ctx, []string{"get"}, crTemplate) { + + err := r.Client.Get(ctx, types.NamespacedName{ + Name: name, + Namespace: namespace, + }, &crTemplate) + if err != nil && !apierrors.IsNotFound(err) { + merr.Add(err) + continue + } + if apierrors.IsNotFound(err) { + klog.V(2).Info("Finish Deleting the CR: " + kind) + continue + } + if r.CheckLabel(crTemplate, map[string]string{constant.OpreqLabel: "true"}) { + wg.Add(1) + go func() { + defer wg.Done() + if r.checkResAuth(ctx, []string{"delete"}, crTemplate) { + if err := r.deleteCustomResource(ctx, crTemplate, namespace); err != nil { + r.Mutex.Lock() + defer r.Mutex.Unlock() + merr.Add(err) + return + } + } + }() + } + + } + + } + + } + } + wg.Wait() + if len(merr.Errors) != 0 { + klog.Errorf("Failed to delete custom resource from OperandConfig for operator %s", operandName) + return merr + } + + return nil +} + +func (r *Reconciler) compareConfigandExample(ctx context.Context, crTemplate unstructured.Unstructured, service *operatorv1alpha1.ConfigService, namespace string) error { + kind := crTemplate.GetKind() + + for crdName, crdConfig := range service.Spec { + // Compare the name of OperandConfig and CRD name + if strings.EqualFold(kind, crdName) { + klog.V(3).Info("Found OperandConfig spec for custom resource: " + kind) + err := r.createCustomResource(ctx, crTemplate, namespace, crdName, crdConfig.Raw) + if err != nil { + return errors.Wrapf(err, "failed to create custom resource -- Kind: %s", kind) + } + } + } + return nil +} + +func (r *Reconciler) createCustomResource(ctx context.Context, crTemplate unstructured.Unstructured, namespace, crName string, crConfig []byte) error { + + //Convert CR template spec to string + specJSONString, _ := json.Marshal(crTemplate.Object["spec"]) + + // Merge CR template spec and OperandConfig spec + mergedCR := util.MergeCR(specJSONString, crConfig) + + crTemplate.Object["spec"] = mergedCR + crTemplate.SetNamespace(namespace) + + r.EnsureLabel(crTemplate, map[string]string{constant.OpreqLabel: "true"}) + + // Create the CR + crerr := r.Create(ctx, &crTemplate) + if crerr != nil && !apierrors.IsAlreadyExists(crerr) { + return errors.Wrap(crerr, "failed to create custom resource") + } + + klog.V(2).Info("Finish creating the Custom Resource: ", crName) + + return nil +} + +func (r *Reconciler) existingCustomResource(ctx context.Context, existingCR unstructured.Unstructured, specFromALM map[string]interface{}, service *operatorv1alpha1.ConfigService, namespace string) error { + kind := existingCR.GetKind() + + var found bool + for crName, crdConfig := range service.Spec { + // Compare the name of OperandConfig and CRD name + if strings.EqualFold(kind, crName) { + found = true + klog.V(3).Info("Found OperandConfig spec for custom resource: " + kind) + err := r.updateCustomResource(ctx, existingCR, namespace, crName, crdConfig.Raw, specFromALM) + if err != nil { + return errors.Wrap(err, "failed to update custom resource") + } + } + } + if !found { + err := r.deleteCustomResource(ctx, existingCR, namespace) + if err != nil { + return err + } + } + return nil +} + +func (r *Reconciler) updateCustomResource(ctx context.Context, existingCR unstructured.Unstructured, namespace, crName string, crConfig []byte, configFromALM map[string]interface{}, owners ...metav1.Object) error { + + kind := existingCR.GetKind() + apiversion := existingCR.GetAPIVersion() + name := existingCR.GetName() + // Update the CR + err := wait.PollImmediate(constant.DefaultCRFetchPeriod, constant.DefaultCRFetchTimeout, func() (bool, error) { + + existingCR := unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": apiversion, + "kind": kind, + }, + } + + err := r.Client.Get(ctx, types.NamespacedName{ + Name: name, + Namespace: namespace, + }, &existingCR) + + if err != nil { + return false, errors.Wrapf(err, "failed to get custom resource -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name) + } + + if !r.CheckLabel(existingCR, map[string]string{constant.OpreqLabel: "true"}) { + return true, nil + } + + forceUpdate := false + for _, owner := range owners { + if err := controllerutil.SetOwnerReference(owner, &existingCR, r.Scheme); err != nil { + return false, errors.Wrapf(err, "failed to set ownerReference for custom resource %s/%s", existingCR.GetNamespace(), existingCR.GetName()) + } + forceUpdate = true + } + + configFromALMRaw, err := json.Marshal(configFromALM) + if err != nil { + klog.Error(err) + return false, err + } + + existingCRRaw, err := json.Marshal(existingCR.Object["spec"]) + if err != nil { + klog.Error(err) + return false, err + } + + // Merge spec from ALM example and existing CR + updatedExistingCR := util.MergeCR(configFromALMRaw, existingCRRaw) + + updatedExistingCRRaw, err := json.Marshal(updatedExistingCR) + if err != nil { + klog.Error(err) + return false, err + } + + // Merge spec from update existing CR and OperandConfig spec + updatedCRSpec := util.MergeCR(updatedExistingCRRaw, crConfig) + + if equality.Semantic.DeepEqual(existingCR.Object["spec"], updatedCRSpec) && !forceUpdate { + return true, nil + } + + CRgeneration := existingCR.GetGeneration() + + klog.V(2).Infof("updating custom resource with apiversion: %s, kind: %s, %s/%s", apiversion, kind, namespace, name) + + existingCR.Object["spec"] = updatedCRSpec + err = r.Update(ctx, &existingCR) + + if err != nil { + return false, errors.Wrapf(err, "failed to update custom resource -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name) + } + + UpdatedCR := unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": apiversion, + "kind": kind, + }, + } + + err = r.Client.Get(ctx, types.NamespacedName{ + Name: name, + Namespace: namespace, + }, &UpdatedCR) + + if err != nil { + return false, errors.Wrapf(err, "failed to get custom resource -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name) + + } + + if UpdatedCR.GetGeneration() != CRgeneration { + klog.V(2).Info("Finish updating the Custom Resource: ", crName) + } + + return true, nil + }) + + if err != nil { + return errors.Wrapf(err, "failed to update custom resource -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name) + } + + return nil +} + +func (r *Reconciler) deleteCustomResource(ctx context.Context, existingCR unstructured.Unstructured, namespace string) error { + + kind := existingCR.GetKind() + apiversion := existingCR.GetAPIVersion() + name := existingCR.GetName() + + crShouldBeDeleted := unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": apiversion, + "kind": kind, + }, + } + err := r.Client.Get(ctx, types.NamespacedName{ + Name: name, + Namespace: namespace, + }, &crShouldBeDeleted) + if err != nil && !apierrors.IsNotFound(err) { + return errors.Wrapf(err, "failed to get custom resource -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name) + } + if apierrors.IsNotFound(err) { + klog.V(3).Infof("There is no custom resource: %s from custom resource definition: %s", name, kind) + } else { + if r.CheckLabel(crShouldBeDeleted, map[string]string{constant.OpreqLabel: "true"}) && !r.CheckLabel(crShouldBeDeleted, map[string]string{constant.NotUninstallLabel: "true"}) { + err := r.Delete(ctx, &crShouldBeDeleted) + if err != nil && !apierrors.IsNotFound(err) { + return errors.Wrapf(err, "failed to delete custom resource -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name) + } + err = wait.PollImmediate(constant.DefaultCRDeletePeriod, constant.DefaultCRDeleteTimeout, func() (bool, error) { + if strings.EqualFold(kind, "OperandRequest") { + return true, nil + } + klog.V(3).Infof("Waiting for CR %s to be removed ...", kind) + err := r.Client.Get(ctx, types.NamespacedName{ + Name: name, + Namespace: namespace, + }, &existingCR) + if apierrors.IsNotFound(err) { + return true, nil + } + if err != nil { + return false, errors.Wrapf(err, "failed to get custom resource -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name) + } + return false, nil + }) + if err != nil { + return errors.Wrapf(err, "failed to delete custom resource -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name) + } + klog.V(1).Infof("Finish deleting custom resource -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name) + } + } + return nil +} + +func (r *Reconciler) checkCustomResource(ctx context.Context, requestInstance *operatorv1alpha1.OperandRequest) error { + klog.V(3).Infof("deleting the custom resource from OperandRequest %s/%s", requestInstance.Namespace, requestInstance.Name) + + members := requestInstance.Status.Members + + customeResourceMap := make(map[string]operatorv1alpha1.OperandCRMember) + for _, member := range members { + if len(member.OperandCRList) != 0 { + for _, cr := range member.OperandCRList { + customeResourceMap[member.Name+"/"+cr.Kind+"/"+cr.Name] = cr + } + } + } + for _, req := range requestInstance.Spec.Requests { + for _, opd := range req.Operands { + if opd.Kind != "" { + var name string + if opd.InstanceName == "" { + name = requestInstance.Name + } else { + name = opd.InstanceName + } + delete(customeResourceMap, opd.Name+"/"+opd.Kind+"/"+name) + } + } + } + + var ( + wg sync.WaitGroup + ) + + merr := &util.MultiErr{} + for index, opdMember := range customeResourceMap { + crShouldBeDeleted := unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": opdMember.APIVersion, + "kind": opdMember.Kind, + "metadata": map[string]interface{}{ + "name": opdMember.Name, + }, + }, + } + + var ( + operatorName = strings.Split(index, "/")[0] + opdMember = opdMember + ) + wg.Add(1) + go func() { + defer wg.Done() + if err := r.deleteCustomResource(ctx, crShouldBeDeleted, requestInstance.Namespace); err != nil { + r.Mutex.Lock() + defer r.Mutex.Unlock() + merr.Add(err) + return + } + requestInstance.RemoveMemberCRStatus(operatorName, opdMember.Name, opdMember.Kind, &r.Mutex) + }() + } + wg.Wait() + + if len(merr.Errors) != 0 { + return merr + } + + return nil +} + +func (r *Reconciler) createK8sResource(ctx context.Context, k8sResTemplate unstructured.Unstructured, k8sResConfig *runtime.RawExtension, newLabels, newAnnotations map[string]string, ownerReferences *[]operatorv1alpha1.OwnerReference, optionalFields *[]operatorv1alpha1.OptionalField) error { + kind := k8sResTemplate.GetKind() + name := k8sResTemplate.GetName() + namespace := k8sResTemplate.GetNamespace() + + if k8sResConfig != nil { + k8sResConfigDecoded := make(map[string]interface{}) + k8sResConfigUnmarshalErr := json.Unmarshal(k8sResConfig.Raw, &k8sResConfigDecoded) + if k8sResConfigUnmarshalErr != nil { + klog.Errorf("failed to unmarshal k8s Resource Config: %v", k8sResConfigUnmarshalErr) + } + for k, v := range k8sResConfigDecoded { + k8sResTemplate.Object[k] = v + } + + k8sResConfigBytes, err := json.Marshal(k8sResConfigDecoded) + if err != nil { + return errors.Wrap(err, "failed to marshal k8sResConfigDecoded") + } + + // Caculate the hash number of the new created template + _, templateHash := util.CalculateResHashes(nil, k8sResConfigBytes) + + newAnnotations = util.AddHashAnnotation(&k8sResTemplate, constant.K8sHashedData, templateHash, newAnnotations) + + if kind == "Route" { + if host, found := k8sResConfigDecoded["spec"].(map[string]interface{})["host"].(string); found { + hostHash := util.CalculateHash([]byte(host)) + + // if newAnnotations == nil { + // newAnnotations = make(map[string]string) + // } + // newAnnotations[constant.RouteHash] = hostHash + newAnnotations = util.AddHashAnnotation(&k8sResTemplate, constant.RouteHash, hostHash, newAnnotations) + + } else { + klog.Warningf("spec.host not found in Route %s/%s", namespace, name) + } + } + } + + if err := r.ExecuteOptionalFields(ctx, &k8sResTemplate, optionalFields); err != nil { + return errors.Wrap(err, "failed to execute optional fields") + } + r.EnsureLabel(k8sResTemplate, map[string]string{constant.OpreqLabel: "true"}) + r.EnsureLabel(k8sResTemplate, newLabels) + r.EnsureAnnotation(k8sResTemplate, newAnnotations) + if err := r.setOwnerReferences(ctx, &k8sResTemplate, ownerReferences); err != nil { + return errors.Wrap(err, "failed to set ownerReferences for k8s resource") + } + + // Create the k8s resource + err := r.Create(ctx, &k8sResTemplate) + if err != nil && !apierrors.IsAlreadyExists(err) { + return errors.Wrap(err, "failed to create k8s resource") + } + + klog.V(2).Infof("Finish creating the k8s Resource: -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name) + + return nil +} + +func (r *Reconciler) updateK8sResource(ctx context.Context, existingK8sRes unstructured.Unstructured, k8sResConfig *runtime.RawExtension, newLabels, newAnnotations map[string]string, ownerReferences *[]operatorv1alpha1.OwnerReference, optionalFields *[]operatorv1alpha1.OptionalField) error { + kind := existingK8sRes.GetKind() + apiversion := existingK8sRes.GetAPIVersion() + name := existingK8sRes.GetName() + namespace := existingK8sRes.GetNamespace() + + if kind == "Job" { + if err := r.updateK8sJob(ctx, existingK8sRes, k8sResConfig, newLabels, newAnnotations, ownerReferences, optionalFields); err != nil { + return errors.Wrap(err, "failed to update Job") + } + return nil + } + + if kind == "Route" { + if err := r.updateK8sRoute(ctx, existingK8sRes, k8sResConfig, newLabels, newAnnotations, ownerReferences, optionalFields); err != nil { + return errors.Wrap(err, "failed to update Route") + } + + // update the annotations of the Route host if the host is changed + if k8sResConfig != nil { + k8sResConfigDecoded := make(map[string]interface{}) + if k8sResConfigUnmarshalErr := json.Unmarshal(k8sResConfig.Raw, &k8sResConfigDecoded); k8sResConfigUnmarshalErr != nil { + return errors.Wrap(k8sResConfigUnmarshalErr, "failed to unmarshal k8s Resource Config") + } + + if host, found := k8sResConfigDecoded["spec"].(map[string]interface{})["host"].(string); found { + hostHash := util.CalculateHash([]byte(host)) + + if newAnnotations == nil { + newAnnotations = make(map[string]string) + } + newAnnotations[constant.RouteHash] = hostHash + } else { + klog.Warningf("spec.host not found in Route %s/%s", namespace, name) + } + } + } + + // Update the k8s resource + err := wait.PollImmediate(constant.DefaultCRFetchPeriod, constant.DefaultCRFetchTimeout, func() (bool, error) { + + existingRes := unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": apiversion, + "kind": kind, + }, + } + + err := r.Client.Get(ctx, types.NamespacedName{ + Name: name, + Namespace: namespace, + }, &existingRes) + if err != nil { + return false, errors.Wrapf(err, "failed to get k8s resource -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name) + } + + resourceVersion := existingRes.GetResourceVersion() + + if !r.CheckLabel(existingRes, map[string]string{constant.OpreqLabel: "true"}) && (newLabels == nil || newLabels[constant.OpreqLabel] != "true") { + return true, nil + } + + if k8sResConfig != nil { + + // Convert existing k8s resource to string + existingResRaw, err := json.Marshal(existingRes.Object) + if err != nil { + return false, errors.Wrapf(err, "failed to marshal existing k8s resource -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name) + } + + // Caculate the hash number of the new created template + existingHash, templateHash := util.CalculateResHashes(&existingRes, k8sResConfig.Raw) + + // If the hash number of the existing k8s resource is different from the hash number of template, update the k8s resource + if existingHash != templateHash { + k8sResConfigDecoded := make(map[string]interface{}) + k8sResConfigUnmarshalErr := json.Unmarshal(k8sResConfig.Raw, &k8sResConfigDecoded) + if k8sResConfigUnmarshalErr != nil { + klog.Errorf("failed to unmarshal k8s Resource Config: %v", k8sResConfigUnmarshalErr) + } + for k, v := range k8sResConfigDecoded { + existingRes.Object[k] = v + } + newAnnotations = util.AddHashAnnotation(&existingRes, constant.K8sHashedData, templateHash, newAnnotations) + + } else { + // If the hash number are the same, then do the deep merge + // Merge the existing CR and the CR from the OperandConfig + updatedExistingRes := util.MergeCR(existingResRaw, k8sResConfig.Raw) + // Update the existing k8s resource with the merged CR + existingRes.Object = updatedExistingRes + } + + if err := r.ExecuteOptionalFields(ctx, &existingRes, optionalFields); err != nil { + return false, errors.Wrap(err, "failed to execute optional fields") + } + r.EnsureAnnotation(existingRes, newAnnotations) + r.EnsureLabel(existingRes, newLabels) + if err := r.setOwnerReferences(ctx, &existingRes, ownerReferences); err != nil { + return false, errors.Wrapf(err, "failed to set ownerReferences for k8s resource -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name) + } + + klog.Infof("updating k8s resource with apiversion: %s, kind: %s, %s/%s", apiversion, kind, namespace, name) + + err = r.Update(ctx, &existingRes) + if err != nil { + return false, errors.Wrapf(err, "failed to update k8s resource -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name) + } + + UpdatedK8sRes := unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": apiversion, + "kind": kind, + }, + } + + err = r.Client.Get(ctx, types.NamespacedName{ + Name: name, + Namespace: namespace, + }, &UpdatedK8sRes) + + if err != nil { + return false, errors.Wrapf(err, "failed to get k8s resource -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name) + + } + + if UpdatedK8sRes.GetResourceVersion() != resourceVersion { + klog.Infof("Finish updating the k8s Resource: -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name) + } else { + klog.Infof("No updates on k8s resource with apiversion: %s, kind: %s, %s/%s", apiversion, kind, namespace, name) + } + + } + return true, nil + }) + + if err != nil { + return errors.Wrapf(err, "failed to update k8s resource -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name) + } + + return nil +} + +func (r *Reconciler) updateK8sJob(ctx context.Context, existingK8sRes unstructured.Unstructured, k8sResConfig *runtime.RawExtension, newLabels, newAnnotations map[string]string, ownerReferences *[]operatorv1alpha1.OwnerReference, optionalFields *[]operatorv1alpha1.OptionalField) error { + + kind := existingK8sRes.GetKind() + apiversion := existingK8sRes.GetAPIVersion() + name := existingK8sRes.GetName() + namespace := existingK8sRes.GetNamespace() + + existingRes := unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": apiversion, + "kind": kind, + }, + } + + err := r.Client.Get(ctx, types.NamespacedName{ + Name: name, + Namespace: namespace, + }, &existingRes) + + if err != nil { + return errors.Wrapf(err, "failed to get k8s resource -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name) + } + if !r.CheckLabel(existingRes, map[string]string{constant.OpreqLabel: "true"}) && (newLabels == nil || newLabels[constant.OpreqLabel] != "true") { + return nil + } + + var existingHashedData string + var newHashedData string + if existingRes.GetAnnotations() != nil { + existingHashedData = existingRes.GetAnnotations()[constant.HashedData] + } + + if k8sResConfig != nil { + hashedData := sha256.Sum256(k8sResConfig.Raw) + newHashedData = hex.EncodeToString(hashedData[:7]) + } + + if existingHashedData != newHashedData { + // create a new template of k8s resource + var templatek8sRes unstructured.Unstructured + templatek8sRes.SetAPIVersion(apiversion) + templatek8sRes.SetKind(kind) + templatek8sRes.SetName(name) + templatek8sRes.SetNamespace(namespace) + + if newAnnotations == nil { + newAnnotations = make(map[string]string) + } + newAnnotations[constant.HashedData] = newHashedData + + if err := r.deleteK8sResource(ctx, existingRes, newLabels, namespace); err != nil { + return errors.Wrap(err, "failed to update k8s resource") + } + if err := r.createK8sResource(ctx, templatek8sRes, k8sResConfig, newLabels, newAnnotations, ownerReferences, optionalFields); err != nil { + return errors.Wrap(err, "failed to update k8s resource") + } + } + return nil +} + +// update route resource +func (r *Reconciler) updateK8sRoute(ctx context.Context, existingK8sRes unstructured.Unstructured, k8sResConfig *runtime.RawExtension, newLabels, newAnnotations map[string]string, ownerReferences *[]operatorv1alpha1.OwnerReference, optionalFields *[]operatorv1alpha1.OptionalField) error { + kind := existingK8sRes.GetKind() + apiversion := existingK8sRes.GetAPIVersion() + name := existingK8sRes.GetName() + namespace := existingK8sRes.GetNamespace() + + existingRes := unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": apiversion, + "kind": kind, + }, + } + err := r.Client.Get(ctx, types.NamespacedName{ + Name: name, + Namespace: namespace, + }, &existingRes) + + if err != nil { + return errors.Wrapf(err, "failed to get k8s resource -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name) + } + if !r.CheckLabel(existingRes, map[string]string{constant.OpreqLabel: "true"}) && (newLabels == nil || newLabels[constant.OpreqLabel] != "true") { + return nil + } + + existingAnnos := existingRes.GetAnnotations() + existingHostHash := existingAnnos[constant.RouteHash] + + if k8sResConfig != nil { + k8sResConfigDecoded := make(map[string]interface{}) + k8sResConfigUnmarshalErr := json.Unmarshal(k8sResConfig.Raw, &k8sResConfigDecoded) + if k8sResConfigUnmarshalErr != nil { + klog.Errorf("failed to unmarshal k8s Resource Config: %v", k8sResConfigUnmarshalErr) + } + + // Read the host from the OperandConfig + if newHost, found := k8sResConfigDecoded["spec"].(map[string]interface{})["host"].(string); found { + newHostHash := util.CalculateHash([]byte(newHost)) + + // Only re-create the route if the custom host has been removed + if newHost == "" && existingHostHash != newHostHash { + + // create a new template of k8s resource + var templatek8sRes unstructured.Unstructured + templatek8sRes.SetAPIVersion(apiversion) + templatek8sRes.SetKind(kind) + templatek8sRes.SetName(name) + templatek8sRes.SetNamespace(namespace) + + if err := r.deleteK8sResource(ctx, existingRes, newLabels, namespace); err != nil { + return errors.Wrap(err, "failed to delete Route for recreation") + } + if err := r.createK8sResource(ctx, templatek8sRes, k8sResConfig, newLabels, newAnnotations, ownerReferences, optionalFields); err != nil { + return errors.Wrap(err, "failed to update k8s resource") + } + } + } + } + return nil +} + +// deleteAllK8sResource remove k8s resource base on OperandConfig +func (r *Reconciler) deleteAllK8sResource(ctx context.Context, csc *operatorv1alpha1.OperandConfig, operandName, namespace string) error { + + service := csc.GetService(operandName) + if service == nil { + return nil + } + + var k8sResourceList []operatorv1alpha1.ConfigResource + k8sResourceList = append(k8sResourceList, service.Resources...) + + merr := &util.MultiErr{} + var ( + wg sync.WaitGroup + ) + for _, k8sRes := range k8sResourceList { + k8sResShouldBeDeleted := unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": k8sRes.APIVersion, + "kind": k8sRes.Kind, + "metadata": map[string]interface{}{ + "name": k8sRes.Name, + }, + }, + } + k8sNamespace := namespace + if k8sRes.Namespace != "" { + k8sNamespace = k8sRes.Namespace + } + + wg.Add(1) + go func() { + defer wg.Done() + if err := r.deleteK8sResource(ctx, k8sResShouldBeDeleted, k8sRes.Labels, k8sNamespace); err != nil { + r.Mutex.Lock() + defer r.Mutex.Unlock() + merr.Add(err) + return + } + }() + } + wg.Wait() + + if len(merr.Errors) != 0 { + return merr + } + return nil +} + +func (r *Reconciler) deleteK8sResource(ctx context.Context, existingK8sRes unstructured.Unstructured, newLabels map[string]string, namespace string) error { + + kind := existingK8sRes.GetKind() + apiversion := existingK8sRes.GetAPIVersion() + name := existingK8sRes.GetName() + + k8sResShouldBeDeleted := unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": apiversion, + "kind": kind, + }, + } + err := r.Client.Get(ctx, types.NamespacedName{ + Name: name, + Namespace: namespace, + }, &k8sResShouldBeDeleted) + if err != nil && !apierrors.IsNotFound(err) { + return errors.Wrapf(err, "failed to get k8s resource -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name) + } + if apierrors.IsNotFound(err) { + klog.V(3).Infof("There is no k8s resource: %s from kind: %s", name, kind) + } else { + // If the existing k8s resources has the OpreqLabel and does not have the NotUninstallLabel, delete it + // If the OpreqLabel is difined in OperandConfig resource, delete it + hasOpreqLabel := r.CheckLabel(k8sResShouldBeDeleted, map[string]string{constant.OpreqLabel: "true"}) + hasNotUninstallLabel := r.CheckLabel(k8sResShouldBeDeleted, map[string]string{constant.NotUninstallLabel: "true"}) + opreqLabelInConfig := newLabels != nil && newLabels[constant.OpreqLabel] == "true" + + if (hasOpreqLabel && !hasNotUninstallLabel) || opreqLabelInConfig { + klog.V(3).Infof("Deleting k8s resource: %s from kind: %s", name, kind) + err := r.Delete(ctx, &k8sResShouldBeDeleted, client.PropagationPolicy(metav1.DeletePropagationBackground)) + if err != nil && !apierrors.IsNotFound(err) { + return errors.Wrapf(err, "failed to delete k8s resource -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name) + } + err = wait.PollImmediate(constant.DefaultCRDeletePeriod, constant.DefaultCRDeleteTimeout, func() (bool, error) { + if strings.EqualFold(kind, "OperandRequest") { + return true, nil + } + klog.V(3).Infof("Waiting for resource %s to be removed ...", kind) + err := r.Client.Get(ctx, types.NamespacedName{ + Name: name, + Namespace: namespace, + }, &existingK8sRes) + if apierrors.IsNotFound(err) { + return true, nil + } + if err != nil { + return false, errors.Wrapf(err, "failed to get k8s resource -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name) + } + return false, nil + }) + if err != nil { + return errors.Wrapf(err, "failed to delete k8s resource -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name) + } + klog.V(1).Infof("Finish deleting k8s resource -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name) + } + } + return nil +} + +func (r *Reconciler) checkResAuth(ctx context.Context, verbs []string, k8sResTemplate unstructured.Unstructured) bool { + kind := k8sResTemplate.GetKind() + apiversion := k8sResTemplate.GetAPIVersion() + name := k8sResTemplate.GetName() + namespace := k8sResTemplate.GetNamespace() + + dc := discovery.NewDiscoveryClientForConfigOrDie(r.Config) + if namespaced, err := util.ResourceNamespaced(dc, apiversion, kind); err != nil { + klog.Errorf("Failed to check resource scope for Kind: %s, NamespacedName: %s/%s, %v", kind, namespace, name, err) + } else if !namespaced { + namespace = "" + } + + gvk := schema.FromAPIVersionAndKind(apiversion, kind) + gvr, err := r.ResourceForKind(gvk, namespace) + if err != nil { + klog.Errorf("Failed to get GroupVersionResource from GroupVersionKind, %v", err) + return false + } + + for _, verb := range verbs { + sar := &authorizationv1.SelfSubjectAccessReview{ + Spec: authorizationv1.SelfSubjectAccessReviewSpec{ + ResourceAttributes: &authorizationv1.ResourceAttributes{ + Namespace: namespace, + Verb: verb, + Group: gvr.Group, + Resource: gvr.Resource, + }, + }, + } + if err := r.Create(ctx, sar); err != nil { + klog.Errorf("Failed to check operator permission for Kind: %s, NamespacedName: %s/%s, %v", kind, namespace, name, err) + return false + } + + klog.V(2).Infof("Operator %s permission in namespace %s for Kind: %s, Allowed: %t, Denied: %t, Reason: %s", verb, namespace, kind, sar.Status.Allowed, sar.Status.Denied, sar.Status.Reason) + + if !sar.Status.Allowed { + return false + } + } + return true +} + +func (r *Reconciler) ResourceForKind(gvk schema.GroupVersionKind, namespace string) (*schema.GroupVersionResource, error) { + mapper := meta.NewDefaultRESTMapper([]schema.GroupVersion{gvk.GroupVersion()}) + + if namespace != "" { + mapper.Add(gvk, meta.RESTScopeRoot) + } else { + mapper.Add(gvk, meta.RESTScopeNamespace) + } + + mapping, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version) + if err != nil { + return nil, err + } + return &mapping.Resource, nil +} + +func (r *Reconciler) setOwnerReferences(ctx context.Context, controlledRes *unstructured.Unstructured, ownerReferences *[]operatorv1alpha1.OwnerReference) error { + if ownerReferences != nil { + for _, owner := range *ownerReferences { + ownerObj := unstructured.Unstructured{} + ownerObj.SetAPIVersion(owner.APIVersion) + ownerObj.SetKind(owner.Kind) + ownerObj.SetName(owner.Name) + + if err := r.Reader.Get(ctx, types.NamespacedName{ + Name: owner.Name, + Namespace: controlledRes.GetNamespace(), + }, &ownerObj); err != nil { + return errors.Wrapf(err, "failed to get owner object -- Kind: %s, NamespacedName: %s/%s", owner.Kind, controlledRes.GetNamespace(), owner.Name) + } + if owner.Controller != nil && *owner.Controller { + if err := controllerutil.SetControllerReference(&ownerObj, controlledRes, r.Scheme); err != nil { + return errors.Wrapf(err, "failed to set controller ownerReference for k8s resource -- Kind: %s, NamespacedName: %s/%s", controlledRes.GetKind(), controlledRes.GetNamespace(), controlledRes.GetName()) + } + } else { + if err := controllerutil.SetOwnerReference(&ownerObj, controlledRes, r.Scheme); err != nil { + return errors.Wrapf(err, "failed to set ownerReference for k8s resource -- Kind: %s, NamespacedName: %s/%s", controlledRes.GetKind(), controlledRes.GetNamespace(), controlledRes.GetName()) + } + } + klog.Infof("Set %s with name %s as Owner for k8s resource -- Kind: %s, NamespacedName: %s/%s", owner.Kind, owner.Name, controlledRes.GetKind(), controlledRes.GetNamespace(), controlledRes.GetName()) + } + } + return nil +} + +func (r *Reconciler) ExecuteOptionalFields(ctx context.Context, resTemplate *unstructured.Unstructured, optionalFields *[]operatorv1alpha1.OptionalField) error { + if optionalFields != nil { + for _, field := range *optionalFields { + // Find the path from resTemplate + if value, err := util.SanitizeObjectString(field.Path, resTemplate.Object); err != nil || value == "" { + klog.Warningf("Skipping execute optional field, not find the path %s in the object -- Kind: %s, NamespacedName: %s/%s: %v", field.Path, resTemplate.GetKind(), resTemplate.GetNamespace(), resTemplate.GetName(), err) + continue + } + // Find the match expressions + if field.MatchExpressions != nil { + if !r.findMatchExpressions(ctx, field.MatchExpressions) { + klog.Infof("Skip operation '%s' for optional fields: %v for %s %s/%s", field.Operation, field.Path, resTemplate.GetKind(), resTemplate.GetNamespace(), resTemplate.GetName()) + continue + } + } + // Do operation + switch field.Operation { + case operatorv1alpha1.OperationRemove: + util.RemoveObjectField(resTemplate.Object, field.Path) + // case "Add": # TODO + default: + klog.Warningf("Invalid operation '%s' in optional fields: %v", field.Operation, field) + } + } + } + return nil +} + +func (r *Reconciler) findMatchExpressions(ctx context.Context, matchExpressions []operatorv1alpha1.MatchExpression) bool { + isMatch := false + for _, matchExpression := range matchExpressions { + if matchExpression.Key == "" || matchExpression.Operator == "" || matchExpression.ObjectRef == nil { + klog.Warningf("Invalid matchExpression: %v", matchExpression) + continue + } + // find value from the object + objRef := &unstructured.Unstructured{} + objRef.SetAPIVersion(matchExpression.ObjectRef.APIVersion) + objRef.SetKind(matchExpression.ObjectRef.Kind) + objRef.SetName(matchExpression.ObjectRef.Name) + if err := r.Reader.Get(ctx, types.NamespacedName{ + Name: matchExpression.ObjectRef.Name, + Namespace: matchExpression.ObjectRef.Namespace, + }, objRef); err != nil { + klog.Warningf("Failed to get the object %v in match expressions: %v", matchExpression.ObjectRef, err) + continue + } + value, _ := util.SanitizeObjectString(matchExpression.Key, objRef.Object) + + switch matchExpression.Operator { + case operatorv1alpha1.OperatorIn: + if util.Contains(matchExpression.Values, value) { + return true + } + case operatorv1alpha1.OperatorNotIn: + if util.Contains(matchExpression.Values, value) { + return false + } else { + isMatch = true + } + case operatorv1alpha1.OperatorExists: + if value != "" { + return true + } + case operatorv1alpha1.OperatorDoesNotExist: + if value != "" { + return false + } else { + isMatch = true + } + default: + klog.Warningf("Invalid operator %s in match expressions: %v", matchExpression.Operator, matchExpression) + } + } + + return isMatch +} + +func (r *Reconciler) ServiceStatusIsReady(ctx context.Context, requestInstance *operatorv1alpha1.OperandRequest) (bool, error) { + requestedServicesSet := make(map[string]struct{}) + for _, req := range requestInstance.Spec.Requests { + registryKey := requestInstance.GetRegistryKey(req) + registryInstance, err := r.GetOperandRegistry(ctx, registryKey) + if err != nil { + klog.Errorf("Failed to get OperandRegistry %s, %v", registryKey, err) + return false, err + } + if registryInstance.Annotations != nil && registryInstance.Annotations[constant.StatusMonitoredServices] != "" { + monitoredServices := strings.Split(registryInstance.Annotations[constant.StatusMonitoredServices], ",") + for _, operand := range req.Operands { + if util.Contains(monitoredServices, operand.Name) { + requestedServicesSet[operand.Name] = struct{}{} + } + } + } + } + + if len(requestedServicesSet) == 0 { + klog.V(2).Infof("No services to be monitored for OperandRequest %s/%s", requestInstance.Namespace, requestInstance.Name) + return true, nil + } + + if len(requestInstance.Status.Services) == 0 { + klog.Infof("Waiting for status.services to be instantiated for OperandRequest %s/%s ...", requestInstance.Namespace, requestInstance.Name) + return false, nil + } + if len(requestedServicesSet) != len(requestInstance.Status.Services) { + klog.Infof("Waiting for status of all requested services to be instantiated for OperandRequest %s/%s ...", requestInstance.Namespace, requestInstance.Name) + return false, nil + } + + serviceStatus := true + // wait for the status of the requested services to be ready + for _, s := range requestInstance.Status.Services { + if _, ok := requestedServicesSet[s.OperatorName]; ok { + if s.Status != "Ready" { + klog.Infof("Waiting for status of service %s to be Ready for OperandRequest %s/%s ...", s.OperatorName, requestInstance.Namespace, requestInstance.Name) + serviceStatus = false + } + } + } + return serviceStatus, nil +} diff --git a/controllers/operandrequestnoolm/reconcile_operator.go b/controllers/operandrequestnoolm/reconcile_operator.go new file mode 100644 index 00000000..47f2bd5a --- /dev/null +++ b/controllers/operandrequestnoolm/reconcile_operator.go @@ -0,0 +1,632 @@ +// +// Copyright 2022 IBM Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package operandrequestnoolm + +import ( + "context" + "encoding/json" + "fmt" + "regexp" + "strings" + "sync" + "time" + + gset "github.com/deckarep/golang-set" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/equality" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + utilerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/klog" + "sigs.k8s.io/controller-runtime/pkg/client" + + operatorv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/v4/api/v1alpha1" + "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/constant" + "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/util" +) + +func (r *Reconciler) reconcileOperator(ctx context.Context, requestInstance *operatorv1alpha1.OperandRequest) error { + klog.V(1).Infof("Reconciling Operators for OperandRequest: %s/%s", requestInstance.GetNamespace(), requestInstance.GetName()) + // It is important to NOT pass the set directly into defer functions. + // The arguments to the deferred function are evaluated when the defer executes + remainingOperands := gset.NewSet() + for _, m := range requestInstance.Status.Members { + remainingOperands.Add(m.Name) + } + // Update request status + defer func() { + requestInstance.FreshMemberStatus(&remainingOperands) + requestInstance.UpdateClusterPhase() + }() + + for _, req := range requestInstance.Spec.Requests { + registryKey := requestInstance.GetRegistryKey(req) + registryInstance, err := r.GetOperandRegistry(ctx, registryKey) + if err != nil { + if apierrors.IsNotFound(err) { + r.Recorder.Eventf(requestInstance, corev1.EventTypeWarning, "NotFound", "NotFound OperandRegistry NamespacedName %s", registryKey.String()) + requestInstance.SetNotFoundOperatorFromRegistryCondition(registryKey.String(), operatorv1alpha1.ResourceTypeOperandRegistry, corev1.ConditionTrue, &r.Mutex) + } else { + requestInstance.SetNoSuitableRegistryCondition(registryKey.String(), err.Error(), operatorv1alpha1.ResourceTypeOperandRegistry, corev1.ConditionTrue, &r.Mutex) + } + t := time.Now() + formatted := fmt.Sprintf("%d-%02d-%02dT%02d:%02d:%02d", + t.Year(), t.Month(), t.Day(), + t.Hour(), t.Minute(), t.Second()) + mergePatch, _ := json.Marshal(map[string]interface{}{ + "metadata": map[string]interface{}{ + "annotations": map[string]interface{}{ + constant.FindOperandRegistry: formatted, + }, + }, + }) + if patchErr := r.Patch(ctx, requestInstance, client.RawPatch(types.MergePatchType, mergePatch)); patchErr != nil { + return utilerrors.NewAggregate([]error{err, patchErr}) + } + klog.Errorf("Failed to get suitable OperandRegistry %s: %v", registryKey.String(), err) + } + merr := &util.MultiErr{} + + // Get the chunk size + var chunkSize int + if r.StepSize > 0 { + chunkSize = r.StepSize + } else { + chunkSize = 1 + } + + // reconcile tracking configmaps in batch + for i := 0; i < len(req.Operands); i += chunkSize { + j := i + chunkSize + if j > len(req.Operands) { + j = len(req.Operands) + } + var ( + wg sync.WaitGroup + ) + for _, operand := range req.Operands[i:j] { + wg.Add(1) + go func(ctx context.Context, requestInstance *operatorv1alpha1.OperandRequest, registryInstance *operatorv1alpha1.OperandRegistry, operand operatorv1alpha1.Operand, registryKey types.NamespacedName, mu *sync.Mutex) { + defer wg.Done() + if err := r.reconcileOpReqCM(ctx, requestInstance, registryInstance, operand, registryKey, mu); err != nil { + mu.Lock() + defer mu.Unlock() + merr.Add(err) + } + }(ctx, requestInstance, registryInstance, operand, registryKey, &r.Mutex) + } + wg.Wait() + } + + if len(merr.Errors) != 0 { + return merr + } + } + + // Delete specific operators + if err := r.absentOperatorsAndOperands(ctx, requestInstance, &remainingOperands); err != nil { + return err + } + klog.V(1).Infof("Finished reconciling Operators for OperandRequest: %s/%s", requestInstance.GetNamespace(), requestInstance.GetName()) + + return nil +} + +// In No olm installs, we create empty configmaps that house the annotations ODLM looks for when cleaning up operators once an opreq is deleted +func (r *Reconciler) reconcileOpReqCM(ctx context.Context, requestInstance *operatorv1alpha1.OperandRequest, registryInstance *operatorv1alpha1.OperandRegistry, operand operatorv1alpha1.Operand, registryKey types.NamespacedName, mu sync.Locker) error { + // Check the requested Operand if exist in specific OperandRegistry + var opt *operatorv1alpha1.Operator + if registryInstance != nil { + var err error + opt, err = r.GetOperandFromRegistryNoOLM(ctx, registryInstance, operand.Name) + if err != nil { + return err + } + } + if opt == nil { + if registryInstance != nil { + klog.V(1).Infof("Operator %s not found in the OperandRegistry %s/%s", operand.Name, registryInstance.Namespace, registryInstance.Name) + } + requestInstance.SetNotFoundOperatorFromRegistryCondition(operand.Name, operatorv1alpha1.ResourceTypeSub, corev1.ConditionTrue, mu) + requestInstance.SetMemberStatus(operand.Name, operatorv1alpha1.OperatorNotFound, operatorv1alpha1.ServiceNotFound, mu) + return nil + } + if opt.Scope == operatorv1alpha1.ScopePrivate && requestInstance.Namespace != registryInstance.Namespace { + klog.Warningf("Operator %s is private. It can't be requested from namespace %s", operand.Name, requestInstance.Namespace) + requestInstance.SetOutofScopeCondition(operand.Name, operatorv1alpha1.ResourceTypeSub, corev1.ConditionTrue, mu) + return nil + } + + // Check configmap if exist + namespace := r.GetOperatorNamespace(opt.InstallMode, opt.Namespace) + cm, err := r.GetOpReqCM(ctx, opt.PackageName, namespace, registryInstance.Namespace) + + if cm == nil && err == nil { + if opt.InstallMode == operatorv1alpha1.InstallModeNoop { + requestInstance.SetNoSuitableRegistryCondition(registryKey.String(), opt.Name+" is in maintenance status", operatorv1alpha1.ResourceTypeOperandRegistry, corev1.ConditionTrue, &r.Mutex) + requestInstance.SetMemberStatus(operand.Name, operatorv1alpha1.OperatorRunning, operatorv1alpha1.ServiceRunning, mu) + } else { + // CM does not exist, create a new one + if err = r.createOpReqCM(ctx, requestInstance, opt, registryKey); err != nil { + requestInstance.SetMemberStatus(opt.Name, operatorv1alpha1.OperatorFailed, "", mu) + return err + } + requestInstance.SetMemberStatus(opt.Name, operatorv1alpha1.OperatorInstalling, "", mu) + } + return nil + } else if err != nil { + return err + } + + // Operator existing and managed by OperandRequest controller + if _, ok := cm.Labels[constant.OpreqLabel]; ok { + originalCM := cm.DeepCopy() + var isInScope bool + + if cm.Namespace == opt.Namespace { + isInScope = true + } else { + var nsAnnoSlice []string + namespaceReg, _ := regexp.Compile(`^(.*)\.(.*)\.(.*)\/operatorNamespace`) + for anno, ns := range cm.Annotations { + if namespaceReg.MatchString(anno) { + nsAnnoSlice = append(nsAnnoSlice, ns) + } + } + if len(nsAnnoSlice) != 0 && !util.Contains(nsAnnoSlice, cm.Namespace) { + if r.checkUninstallLabel(cm) { + klog.V(1).Infof("Operator %s has label operator.ibm.com/opreq-do-not-uninstall. Skip the uninstall", opt.Name) + return nil + } + if err = r.deleteOpReqCM(ctx, requestInstance, cm); err != nil { + requestInstance.SetMemberStatus(opt.Name, operatorv1alpha1.OperatorFailed, "", mu) + return err + } + requestInstance.SetMemberStatus(opt.Name, operatorv1alpha1.OperatorUpdating, "", mu) + return nil + } + } + + // add annotations to existing tracking configmap for upgrade case + if cm.Annotations == nil { + cm.Annotations = make(map[string]string) + } + cm.Annotations[registryKey.Namespace+"."+registryKey.Name+"/registry"] = "true" + cm.Annotations[registryKey.Namespace+"."+registryKey.Name+"/config"] = "true" + cm.Annotations[requestInstance.Namespace+"."+requestInstance.Name+"."+operand.Name+"/request"] = opt.Channel + cm.Annotations[requestInstance.Namespace+"."+requestInstance.Name+"."+operand.Name+"/operatorNamespace"] = namespace + cm.Annotations["packageName"] = opt.PackageName + + if opt.InstallMode == operatorv1alpha1.InstallModeNoop { + requestInstance.SetNoSuitableRegistryCondition(registryKey.String(), opt.Name+" is in maintenance status", operatorv1alpha1.ResourceTypeOperandRegistry, corev1.ConditionTrue, &r.Mutex) + requestInstance.SetMemberStatus(operand.Name, operatorv1alpha1.OperatorRunning, operatorv1alpha1.ServiceRunning, mu) + } else { + requestInstance.SetNotFoundOperatorFromRegistryCondition(operand.Name, operatorv1alpha1.ResourceTypeConfigmap, corev1.ConditionFalse, mu) + } + if compareOpReqCM(cm, originalCM) { + if err = r.updateOpReqCM(ctx, requestInstance, cm); err != nil { + requestInstance.SetMemberStatus(opt.Name, operatorv1alpha1.OperatorFailed, "", mu) + return err + } + requestInstance.SetMemberStatus(opt.Name, operatorv1alpha1.OperatorUpdating, "", mu) + } + + if !isInScope { + requestInstance.SetNoConflictOperatorCondition(operand.Name, operatorv1alpha1.ResourceTypeSub, corev1.ConditionFalse, mu) + requestInstance.SetMemberStatus(opt.Name, operatorv1alpha1.OperatorFailed, "", mu) + } else { + requestInstance.SetNoConflictOperatorCondition(operand.Name, operatorv1alpha1.ResourceTypeSub, corev1.ConditionTrue, mu) + } + } else { + // Operator existing and not managed by OperandRequest controller + klog.V(1).Infof("Configmap %s in namespace %s isn't created by ODLM. Ignore update/delete it.", cm.Name, cm.Namespace) + } + return nil +} + +func (r *Reconciler) createOpReqCM(ctx context.Context, cr *operatorv1alpha1.OperandRequest, opt *operatorv1alpha1.Operator, key types.NamespacedName) error { + namespace := r.GetOperatorNamespace(opt.InstallMode, opt.Namespace) + klog.V(3).Info("Cofigmap Namespace: ", namespace) + + co := r.generateClusterObjects(opt, key, types.NamespacedName{Namespace: cr.Namespace, Name: cr.Name}) + + // Create required namespace + ns := co.namespace + klog.V(3).Info("Creating the Namespace for Operator: " + opt.Name) + + // Compare namespace and create namespace + oprNs := util.GetOperatorNamespace() + if ns.Name != oprNs && ns.Name != constant.ClusterOperatorNamespace { + if err := r.Create(ctx, ns); err != nil && !apierrors.IsAlreadyExists(err) { + klog.Warningf("failed to create the namespace %s, please make sure it exists: %s", ns.Name, err) + } + } + + // Create OperandRequest CM + klog.V(2).Info("Creating the Configmap: " + opt.Name) + + cm := co.configmap + cr.SetCreatingCondition(cm.Name, operatorv1alpha1.ResourceTypeConfigmap, corev1.ConditionTrue, &r.Mutex) + + if err := r.Create(ctx, cm); err != nil && !apierrors.IsAlreadyExists(err) { + cr.SetCreatingCondition(cm.Name, operatorv1alpha1.ResourceTypeConfigmap, corev1.ConditionFalse, &r.Mutex) + return err + } + return nil +} + +func (r *Reconciler) deleteOpReqCM(ctx context.Context, cr *operatorv1alpha1.OperandRequest, cm *corev1.ConfigMap) error { + + klog.V(2).Infof("Deleting Subscription %s/%s ...", cm.Namespace, cm.Name) + + klog.V(2).Infof("Deleting the Configmap, Namespace: %s, Name: %s", cm.Namespace, cm.Name) + cr.SetDeletingCondition(cm.Name, operatorv1alpha1.ResourceTypeConfigmap, corev1.ConditionTrue, &r.Mutex) + + if err := r.Delete(ctx, cm); err != nil { + if apierrors.IsNotFound(err) { + klog.Warningf("Configmap %s was not found in namespace %s", cm.Name, cm.Namespace) + } else { + cr.SetDeletingCondition(cm.Name, operatorv1alpha1.ResourceTypeConfigmap, corev1.ConditionFalse, &r.Mutex) + return err + } + } + + klog.V(1).Infof("Configmap %s/%s is deleted", cm.Namespace, cm.Name) + return nil +} + +func (r *Reconciler) updateOpReqCM(ctx context.Context, cr *operatorv1alpha1.OperandRequest, cm *corev1.ConfigMap) error { + + klog.V(2).Infof("Updating Configmap %s/%s ...", cm.Namespace, cm.Name) + cr.SetUpdatingCondition(cm.Name, operatorv1alpha1.ResourceTypeSub, corev1.ConditionTrue, &r.Mutex) + + if err := r.Update(ctx, cm); err != nil { + cr.SetUpdatingCondition(cm.Name, operatorv1alpha1.ResourceTypeSub, corev1.ConditionFalse, &r.Mutex) + return err + } + return nil +} + +func (r *Reconciler) uninstallOperatorsAndOperands(ctx context.Context, operandName string, requestInstance *operatorv1alpha1.OperandRequest, registryInstance *operatorv1alpha1.OperandRegistry, configInstance *operatorv1alpha1.OperandConfig) error { + // No error handling for un-installation step in case Catalog has been deleted + op, _ := r.GetOperandFromRegistryNoOLM(ctx, registryInstance, operandName) + if op == nil { + klog.Warningf("Operand %s not found", operandName) + return nil + } + + namespace := r.GetOperatorNamespace(op.InstallMode, op.Namespace) + + //Assuing we can still use op as a parameter, we should be able to get the deployment with ease + if deploymentList, err := r.GetDeploymentListFromPackage(ctx, op.PackageName, op.Namespace); err != nil { + // If can't get deployment, requeue the request + return err + } else if deploymentList != nil { + + if deploymentList[0].Labels == nil { + // Deployment existing and not managed by OperandRequest controller + klog.V(2).Infof("Deployment %s in the namespace %s isn't created by ODLM", deploymentList[0].Name, deploymentList[0].Namespace) + return nil + } + + cm, err := r.GetOpReqCM(ctx, op.PackageName, deploymentList[0].Namespace, registryInstance.Namespace) + if cm != nil && err == nil { + uninstallOperator, uninstallOperand := checkOpReqCMAnnotationsForUninstall(requestInstance.ObjectMeta.Name, requestInstance.ObjectMeta.Namespace, op.Name, op.InstallMode, cm) + // Updated the Configmap for the OperandRequest + if err = r.updateOpReqCM(ctx, requestInstance, cm); err != nil { + requestInstance.SetMemberStatus(op.Name, operatorv1alpha1.OperatorFailed, "", &r.Mutex) + return err + } + if !uninstallOperand && !uninstallOperator { + requestInstance.SetMemberStatus(op.Name, operatorv1alpha1.OperatorUpdating, "", &r.Mutex) + + klog.V(1).Infof("No deletion, operator %s/%s and its operands are still requested by other OperandRequests", cm.Namespace, cm.Name) + return nil + } + if deploymentList, err := r.GetDeploymentListFromPackage(ctx, op.PackageName, op.Namespace); err != nil { + // If can't get deployment, requeue the request + return err + } else if deploymentList != nil { + klog.Infof("Found %d Deployment for package %s/%s", len(deploymentList), op.Name, namespace) + if uninstallOperand { + + klog.V(1).Infof("Deleting all the Custom Resources for Deployment, Namespace: %s, Name: %s", deploymentList[0].Namespace, deploymentList[0].Name) + if err := r.deleteAllCustomResource(ctx, deploymentList[0], requestInstance, configInstance, operandName, configInstance.Namespace); err != nil { + return err + } + klog.V(1).Infof("Deleting all the k8s Resources for Deployment, Namespace: %s, Name: %s", deploymentList[0].Namespace, deploymentList[0].Name) + if err := r.deleteAllK8sResource(ctx, configInstance, operandName, configInstance.Namespace); err != nil { + return err + } + } + // TODO should odlm delete deployments or should that be handled by helm? + // Keep this part for OperandRequest Configmap deletion + if uninstallOperator { + if r.checkUninstallLabel(cm) { + klog.V(1).Infof("Operator %s has label operator.ibm.com/opreq-do-not-uninstall. Skip the uninstall", op.Name) + return nil + } + + // Delete the OperandRequest Configmap + if err := r.deleteOpReqCM(ctx, requestInstance, cm); err != nil { + return err + } + } + } + } else if err != nil { + klog.Errorf("Failed to get Configmap called %s or using package name %s in the namespace %s and %s", operandName, op.PackageName, namespace, registryInstance.Namespace) + return err + } + } + return nil +} + +func (r *Reconciler) uninstallOperands(ctx context.Context, operandName string, requestInstance *operatorv1alpha1.OperandRequest, registryInstance *operatorv1alpha1.OperandRegistry, configInstance *operatorv1alpha1.OperandConfig) error { + // No error handling for un-installation step in case Catalog has been deleted + op, _ := r.GetOperandFromRegistryNoOLM(ctx, registryInstance, operandName) + if op == nil { + klog.Warningf("Operand %s not found", operandName) + return nil + } + + namespace := r.GetOperatorNamespace(op.InstallMode, op.Namespace) + uninstallOperand := false + operatorStatus, ok := registryInstance.Status.OperatorsStatus[op.Name] + if !ok { + return nil + } + if operatorStatus.ReconcileRequests == nil { + return nil + } + if len(operatorStatus.ReconcileRequests) > 1 { + return nil + } + if operatorStatus.ReconcileRequests[0].Name == requestInstance.Name { + uninstallOperand = true + } + + // get list reconcileRequests + // ignore the name which triggered reconcile + // if list is empty then uninstallOperand = true + + if deploymentList, err := r.GetDeploymentListFromPackage(ctx, op.PackageName, op.Namespace); err != nil { + // If can't get deployment, requeue the request + return err + } else if deploymentList != nil { + klog.Infof("Found %d Deployment for package %s/%s", len(deploymentList), op.Name, namespace) + if uninstallOperand { + klog.V(2).Infof("Deleting all the Custom Resources for Deployment, Namespace: %s, Name: %s", deploymentList[0].Namespace, deploymentList[0].Name) + if err := r.deleteAllCustomResource(ctx, deploymentList[0], requestInstance, configInstance, operandName, configInstance.Namespace); err != nil { + return err + } + klog.V(2).Infof("Deleting all the k8s Resources for Deployment, Namespace: %s, Name: %s", deploymentList[0].Namespace, deploymentList[0].Name) + if err := r.deleteAllK8sResource(ctx, configInstance, operandName, configInstance.Namespace); err != nil { + return err + } + } + } + + return nil +} + +func (r *Reconciler) absentOperatorsAndOperands(ctx context.Context, requestInstance *operatorv1alpha1.OperandRequest, remainingOperands *gset.Set) error { + needDeletedOperands := r.getNeedDeletedOperands(requestInstance) + + var ( + wg sync.WaitGroup + ) + + for _, req := range requestInstance.Spec.Requests { + registryKey := requestInstance.GetRegistryKey(req) + registryInstance, err := r.GetOperandRegistry(ctx, registryKey) + if err != nil { + return err + } + configInstance, err := r.GetOperandConfig(ctx, registryKey) + if err != nil { + if apierrors.IsNotFound(err) { + configInstance = &operatorv1alpha1.OperandConfig{} + } else { + return err + } + } + merr := &util.MultiErr{} + remainingOp := needDeletedOperands.Clone() + for o := range needDeletedOperands.Iter() { + var ( + o = o + ) + wg.Add(1) + go func() { + defer wg.Done() + op, _ := r.GetOperandFromRegistryNoOLM(ctx, registryInstance, fmt.Sprintf("%v", o)) + if op == nil { + klog.Warningf("Operand %s not found", fmt.Sprintf("%v", o)) + } + if op != nil && !op.UserManaged { + //TODO do we need to uninstall operators and operands? Should the user uninstall operators with helm uninstall going forward? + //The below function currently does not delete operators + if err := r.uninstallOperatorsAndOperands(ctx, fmt.Sprintf("%v", o), requestInstance, registryInstance, configInstance); err != nil { + r.Mutex.Lock() + defer r.Mutex.Unlock() + merr.Add(err) + return // return here to avoid removing the operand from remainingOperands + } + } else { + if err := r.uninstallOperands(ctx, fmt.Sprintf("%v", o), requestInstance, registryInstance, configInstance); err != nil { + r.Mutex.Lock() + defer r.Mutex.Unlock() + merr.Add(err) + return // return here to avoid removing the operand from remainingOperands + } + } + requestInstance.RemoveServiceStatus(fmt.Sprintf("%v", o), &r.Mutex) + (*remainingOperands).Remove(o) + remainingOp.Remove(o) + // klog.V(1).Info("op removed: ", op.Name, " o: ", fmt.Sprintf("%v", o)) + }() + } + timeout := util.WaitTimeout(&wg, constant.DefaultSubDeleteTimeout) + if timeout { + merr.Add(fmt.Errorf("timeout for cleaning up subscription %v", strings.Trim(fmt.Sprint(remainingOp.ToSlice()), "[]"))) + } + if len(merr.Errors) != 0 { + return merr + } + + } + return nil +} + +func (r *Reconciler) getNeedDeletedOperands(requestInstance *operatorv1alpha1.OperandRequest) gset.Set { + klog.V(3).Info("Getting the operator need to be delete") + deployedOperands := gset.NewSet() + for _, req := range requestInstance.Status.Members { + deployedOperands.Add(req.Name) + } + + currentOperands := gset.NewSet() + if requestInstance.DeletionTimestamp.IsZero() { + for _, req := range requestInstance.Spec.Requests { + for _, op := range req.Operands { + // klog.V(1).Info("Add current operand in getNeedDeletedOperands ", op.Name) + currentOperands.Add(op.Name) + } + } + } + + needDeleteOperands := deployedOperands.Difference(currentOperands) + return needDeleteOperands +} + +func (r *Reconciler) generateClusterObjects(o *operatorv1alpha1.Operator, registryKey, requestKey types.NamespacedName) *clusterObjects { + klog.V(3).Info("Generating Cluster Objects") + co := &clusterObjects{} + labels := map[string]string{ + constant.OpreqLabel: "true", + constant.ODLMWatchedLabel: "true", + constant.OpreqTrackerLabel: "true", + } + + klog.V(3).Info("Generating Namespace: ", o.Namespace) + // Namespace Object + co.namespace = &corev1.Namespace{ + TypeMeta: metav1.TypeMeta{ + Kind: "Namespace", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: o.Namespace, + Labels: labels, + }, + } + + // The namespace is 'openshift-operators' when installMode is cluster + namespace := r.GetOperatorNamespace(o.InstallMode, o.Namespace) + + annotations := map[string]string{ + registryKey.Namespace + "." + registryKey.Name + "/registry": "true", + registryKey.Namespace + "." + registryKey.Name + "/config": "true", + requestKey.Namespace + "." + requestKey.Name + "." + o.Name + "/request": o.Channel, + requestKey.Namespace + "." + requestKey.Name + "." + o.Name + "/operatorNamespace": namespace, + "packageName": o.PackageName, + } + + //CM object + cm := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "opreq-" + o.PackageName, + Namespace: namespace, + Labels: labels, + Annotations: annotations, + }, + Data: map[string]string{}, + } + + cm.SetGroupVersionKind(schema.GroupVersionKind{Group: corev1.SchemeGroupVersion.Group, Kind: "Configmap", Version: corev1.SchemeGroupVersion.Version}) + klog.V(2).Info("Generating tracking Configmap: ", o.PackageName, " in the Namespace: ", namespace) + co.configmap = cm + return co +} + +func (r *Reconciler) checkUninstallLabel(cm *corev1.ConfigMap) bool { + cmLabels := cm.GetLabels() + return cmLabels[constant.NotUninstallLabel] == "true" +} + +func compareOpReqCM(cm *corev1.ConfigMap, originalCM *corev1.ConfigMap) (needUpdate bool) { + return !equality.Semantic.DeepEqual(cm.Annotations, originalCM.Annotations) +} + +func CheckSingletonServices(operator string) bool { + singletonServices := []string{"ibm-cert-manager-operator", "ibm-licensing-operator"} + return util.Contains(singletonServices, operator) +} + +// checkOpReqCMAnnotationsForUninstall checks the annotations of a tracking configmap object +// to determine whether the operator and operand should be uninstalled. +// It takes the name of the OperandRequest, the namespace of the OperandRequest, +// the name of the operator, and a pointer to the configmap object as input. +// It returns two boolean values: uninstallOperator and uninstallOperand. +// If uninstallOperator is true, it means the operator should be uninstalled. +// If uninstallOperand is true, it means the operand should be uninstalled. +func checkOpReqCMAnnotationsForUninstall(reqName, reqNs, opName, installMode string, cm *corev1.ConfigMap) (bool, bool) { + uninstallOperator := true + uninstallOperand := true + + delete(cm.Annotations, reqNs+"."+reqName+"."+opName+"/request") + delete(cm.Annotations, reqNs+"."+reqName+"."+opName+"/operatorNamespace") + + var opreqNsSlice []string + var operatorNameSlice []string + namespaceReg, _ := regexp.Compile(`^(.*)\.(.*)\.(.*)\/operatorNamespace`) + channelReg, _ := regexp.Compile(`^(.*)\.(.*)\.(.*)\/request`) + + for key, value := range cm.Annotations { + if namespaceReg.MatchString(key) { + opreqNsSlice = append(opreqNsSlice, value) + } + + if channelReg.MatchString(key) { + // Extract the operator name from the key + keyParts := strings.Split(key, "/") + annoPrefix := strings.Split(keyParts[0], ".") + operatorNameSlice = append(operatorNameSlice, annoPrefix[len(annoPrefix)-1]) + } + } + + // If one of remaining /operatorNamespace annotations' values is the same as subscription's namespace, + // the operator should NOT be uninstalled. + if util.Contains(opreqNsSlice, cm.Namespace) { + uninstallOperator = false + } + + if value, ok := cm.Labels[constant.OpreqLabel]; !ok || value != "true" { + uninstallOperator = false + } + + // When one of following conditions are met, the operand will NOT be uninstalled: + // 1. operator is not uninstalled AND intallMode is no-op. + // 2. operator is uninstalled AND at least one other /operatorNamespace annotation exists. + // 2. remaining /request annotation's values contain the same operator name + if (!uninstallOperator && installMode == operatorv1alpha1.InstallModeNoop) || (uninstallOperator && len(opreqNsSlice) != 0) || util.Contains(operatorNameSlice, opName) { + uninstallOperand = false + } + return uninstallOperator, uninstallOperand +} diff --git a/controllers/operator/manager.go b/controllers/operator/manager.go index 91f7283f..d759f95e 100644 --- a/controllers/operator/manager.go +++ b/controllers/operator/manager.go @@ -18,13 +18,19 @@ package operator import ( "context" + "encoding/json" "fmt" + "reflect" "sort" "strings" olmv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" operatorsv1 "github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/apis/operators/v1" "github.com/pkg/errors" + "golang.org/x/mod/semver" + appsv1 "k8s.io/api/apps/v1" + authorizationv1 "k8s.io/api/authorization/v1" + corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -35,9 +41,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/manager" - apiv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/api/v1alpha1" - constant "github.com/IBM/operand-deployment-lifecycle-manager/controllers/constant" - "github.com/IBM/operand-deployment-lifecycle-manager/controllers/util" + apiv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/v4/api/v1alpha1" + constant "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/constant" + "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/util" ) // ODLMOperator is the struct for ODLM controllers @@ -45,18 +51,20 @@ type ODLMOperator struct { client.Client client.Reader *rest.Config - Recorder record.EventRecorder - Scheme *runtime.Scheme + Recorder record.EventRecorder + Scheme *runtime.Scheme + MaxConcurrentReconciles int } // NewODLMOperator is the method to initialize an Operator struct func NewODLMOperator(mgr manager.Manager, name string) *ODLMOperator { return &ODLMOperator{ - Client: mgr.GetClient(), - Reader: mgr.GetAPIReader(), - Config: mgr.GetConfig(), - Recorder: mgr.GetEventRecorderFor(name), - Scheme: mgr.GetScheme(), + Client: mgr.GetClient(), + Reader: mgr.GetAPIReader(), + Config: mgr.GetConfig(), + Recorder: mgr.GetEventRecorderFor(name), + Scheme: mgr.GetScheme(), + MaxConcurrentReconciles: 3, } } @@ -66,12 +74,6 @@ func (m *ODLMOperator) GetOperandRegistry(ctx context.Context, key types.Namespa if err := m.Client.Get(ctx, key, reg); err != nil { return nil, err } - // Get excluded CatalogSource from annotation - // excluded-catalogsource: catalogsource1, catalogsource2 - var excludedCatalogSources []string - if reg.Annotations != nil && reg.Annotations["excluded-catalogsource"] != "" { - excludedCatalogSources = strings.Split(reg.Annotations["excluded-catalogsource"], ",") - } for i, o := range reg.Spec.Operators { if o.Scope == "" { @@ -86,30 +88,18 @@ func (m *ODLMOperator) GetOperandRegistry(ctx context.Context, key types.Namespa if o.Namespace == "" { reg.Spec.Operators[i].Namespace = key.Namespace } - if o.SupportStatus == "" { - reg.Spec.Operators[i].SupportStatus = apiv1alpha1.ContinuousSupportStatus - } - if o.SourceName == "" || o.SourceNamespace == "" { - catalogSourceName, catalogSourceNs, err := m.GetCatalogSourceFromPackage(ctx, o.PackageName, o.Namespace, o.Channel, key.Namespace, excludedCatalogSources) - if err != nil { - return reg, err - } - - if catalogSourceName == "" || catalogSourceNs == "" { - klog.V(2).Infof("no catalogsource found for %v", o.PackageName) - } - - reg.Spec.Operators[i].SourceName, reg.Spec.Operators[i].SourceNamespace = catalogSourceName, catalogSourceNs - } } return reg, nil } type CatalogSource struct { - Name string - Namespace string - OpNamespace string - RegistryNamespace string + Name string + Namespace string + OpNamespace string + RegistryNamespace string + Priority int + ODLMCatalog string + ODLMCatalogNamespace string } type sortableCatalogSource []CatalogSource @@ -117,22 +107,33 @@ type sortableCatalogSource []CatalogSource func (s sortableCatalogSource) Len() int { return len(s) } func (s sortableCatalogSource) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s sortableCatalogSource) Less(i, j int) bool { - // Check if the catalogsource is in the same namespace as OperandRegistry - inRegistryNsI, inRegistryNsJ := s[i].Namespace == s[i].RegistryNamespace, s[j].Namespace == s[j].RegistryNamespace - if inRegistryNsI && !inRegistryNsJ { + + // Check if the catalogsource is in the same namespace as operator. + // CatalogSources in operator namespace (private CatalogSource) have a higher priority than those in other namespaces (global CatalogSource). + inOpNsI, inOpNsJ := s[i].Namespace == s[i].OpNamespace, s[j].Namespace == s[j].OpNamespace + if inOpNsI && !inOpNsJ { return true } - if !inRegistryNsI && inRegistryNsJ { + if !inOpNsI && inOpNsJ { return false } - // Check if the catalogsource is in the same namespace as operator - inOpNsI, inOpNsJ := s[i].Namespace == s[i].OpNamespace, s[j].Namespace == s[j].OpNamespace - if inOpNsI && !inOpNsJ { + + // Compare catalogsource priorities first, higher priority comes first + iPriority, jPriority := s[i].Priority, s[j].Priority + if iPriority != jPriority { + return iPriority > jPriority + } + + // Check if the catalogsource is in the same catalog as ODLM itself. + // CatalogSources in the same catalog as ODLM have a higher priority than those in other catalogs. + inODLMNsI, inODLMNsJ := s[i].Name == s[i].ODLMCatalog && s[i].Namespace == s[i].ODLMCatalogNamespace, s[j].Name == s[j].ODLMCatalog && s[j].Namespace == s[j].ODLMCatalogNamespace + if inODLMNsI && !inODLMNsJ { return true } - if !inOpNsI && inOpNsJ { + if !inODLMNsI && inODLMNsJ { return false } + // If their namespaces are the same, then compare the name of the catalogsource if s[i].Namespace == s[j].Namespace { return s[i].Name < s[j].Name @@ -140,46 +141,118 @@ func (s sortableCatalogSource) Less(i, j int) bool { return s[i].Namespace < s[j].Namespace } -func (m *ODLMOperator) GetCatalogSourceFromPackage(ctx context.Context, packageName, namespace, channel, registryNs string, excludedCatalogSources []string) (catalogSourceName string, catalogSourceNs string, err error) { +func (m *ODLMOperator) GetCatalogSourceAndChannelFromPackage(ctx context.Context, opregCatalog, opregCatalogNs, packageName, namespace, channel string, fallbackChannels []string, + registryNs, odlmCatalog, odlmCatalogNs string, excludedCatalogSources []string) (catalogSourceName string, catalogSourceNs string, availableChannel string, err error) { + packageManifestList := &operatorsv1.PackageManifestList{} opts := []client.ListOption{ client.MatchingFields{"metadata.name": packageName}, client.InNamespace(namespace), } if err := m.Reader.List(ctx, packageManifestList, opts...); err != nil { - return "", "", err + return "", "", "", err } number := len(packageManifestList.Items) switch number { case 0: klog.V(2).Infof("Not found PackageManifest %s in the namespace %s has channel %s", packageName, namespace, channel) - return "", "", nil - case 1: - if excludedCatalogSources != nil && util.Contains(excludedCatalogSources, packageManifestList.Items[0].Status.CatalogSource) { - klog.V(2).Infof("Not found available CatalogSource for PackageManifest %s in the namespace %s, CatalogSource %s is excluded from OperandRegistry annotations", packageName, namespace, packageManifestList.Items[0].Status.CatalogSource) - return "", "", nil - } - return packageManifestList.Items[0].Status.CatalogSource, packageManifestList.Items[0].Status.CatalogSourceNamespace, nil + return opregCatalog, opregCatalogNs, channel, nil default: - var catalogSourceCandidate []CatalogSource + // Check if the CatalogSource and CatalogSource namespace are specified in OperandRegistry + if opregCatalog != "" && opregCatalogNs != "" { + curChannel := getFirstAvailableSemverChannelFromCatalog(packageManifestList, fallbackChannels, channel, opregCatalog, opregCatalogNs) + if curChannel == "" { + klog.Errorf("Not found PackageManifest %s in the namespace %s has channel %s or fallback channels %s in the CatalogSource %s in the namespace %s", packageName, namespace, channel, fallbackChannels, opregCatalog, opregCatalogNs) + return "", "", "", nil + } + return opregCatalog, opregCatalogNs, curChannel, nil + } + // Get the CatalogSource and CatalogSource namespace from the PackageManifest + var primaryCatalogCandidate []CatalogSource + var fallBackChannelAndCatalogMapping = make(map[string][]CatalogSource) for _, pm := range packageManifestList.Items { - if !channelCheck(channel, pm.Status.Channels) || (excludedCatalogSources != nil && util.Contains(excludedCatalogSources, pm.Status.CatalogSource)) { + if excludedCatalogSources != nil && util.Contains(excludedCatalogSources, pm.Status.CatalogSource) { + continue + } + + hasCatalogPermission := m.CheckResAuth(ctx, pm.Status.CatalogSourceNamespace, "operators.coreos.com", "catalogsources", "get") + if !hasCatalogPermission { + klog.V(2).Infof("No permission to get CatalogSource %s in the namespace %s", pm.Status.CatalogSource, pm.Status.CatalogSourceNamespace) + continue + } + // Fetch the CatalogSource if cluster permission allows + catalogsource := &olmv1alpha1.CatalogSource{} + if err := m.Reader.Get(ctx, types.NamespacedName{Name: pm.Status.CatalogSource, Namespace: pm.Status.CatalogSourceNamespace}, catalogsource); err != nil { + klog.Warning(err) continue } - catalogSourceCandidate = append(catalogSourceCandidate, CatalogSource{Name: pm.Status.CatalogSource, Namespace: pm.Status.CatalogSourceNamespace, OpNamespace: namespace, RegistryNamespace: registryNs}) + + currentCatalog := CatalogSource{ + Name: pm.Status.CatalogSource, + Namespace: pm.Status.CatalogSourceNamespace, + OpNamespace: namespace, + RegistryNamespace: registryNs, + Priority: catalogsource.Spec.Priority, + ODLMCatalog: odlmCatalog, + ODLMCatalogNamespace: odlmCatalogNs, + } + if channelCheck(channel, pm.Status.Channels) { + primaryCatalogCandidate = append(primaryCatalogCandidate, currentCatalog) + } + for _, fc := range fallbackChannels { + if channelCheck(fc, pm.Status.Channels) { + fallBackChannelAndCatalogMapping[fc] = append(fallBackChannelAndCatalogMapping[fc], currentCatalog) + } + } } - if len(catalogSourceCandidate) == 0 { - klog.Errorf("Not found PackageManifest %s in the namespace %s has channel %s", packageName, namespace, channel) - return "", "", nil + if len(primaryCatalogCandidate) == 0 { + klog.Warningf("Not found PackageManifest %s in the namespace %s has channel %s", packageName, namespace, channel) + if len(fallBackChannelAndCatalogMapping) == 0 { + klog.Errorf("Not found PackageManifest %s in the namespace %s has fallback channels %v", packageName, namespace, fallbackChannels) + return "", "", "", nil + } + fallbackChannel, fallbackCatalog := findCatalogFromFallbackChannels(fallbackChannels, fallBackChannelAndCatalogMapping) + if len(fallbackCatalog) == 0 { + klog.Errorf("Not found PackageManifest %s in the namespace %s has fallback channels %v", packageName, namespace, fallbackChannels) + return "", "", "", nil + } + klog.Infof("Found %v CatalogSources for PackageManifest %s in the namespace %s has fallback channel %s", len(fallbackCatalog), packageName, namespace, fallbackChannel) + primaryCatalogCandidate = fallbackCatalog + channel = fallbackChannel } + klog.V(2).Infof("Found %v CatalogSources for PackageManifest %s in the namespace %s has channel %s", len(primaryCatalogCandidate), packageName, namespace, channel) // Sort CatalogSources by priority - sort.Sort(sortableCatalogSource(catalogSourceCandidate)) - return catalogSourceCandidate[0].Name, catalogSourceCandidate[0].Namespace, nil + sort.Sort(sortableCatalogSource(primaryCatalogCandidate)) + for i, c := range primaryCatalogCandidate { + klog.V(2).Infof("The %vth sorted CatalogSource is %s in namespace %s with priority: %v", i, c.Name, c.Namespace, c.Priority) + } + return primaryCatalogCandidate[0].Name, primaryCatalogCandidate[0].Namespace, channel, nil + } +} + +func (m *ODLMOperator) CheckResAuth(ctx context.Context, namespace, group, resource, verb string) bool { + sar := &authorizationv1.SelfSubjectAccessReview{ + Spec: authorizationv1.SelfSubjectAccessReviewSpec{ + ResourceAttributes: &authorizationv1.ResourceAttributes{ + Namespace: namespace, + Group: group, + Resource: resource, + Verb: verb, + }, + }, + } + if err := m.Create(ctx, sar); err != nil { + klog.Errorf("Failed to check operator permission for Kind %s in namespace %s: %v", resource, namespace, err) + return false } + + klog.V(2).Infof("Operator %s permission in namespace %s for Kind: %s, Allowed: %t, Denied: %t, Reason: %s", verb, namespace, resource, sar.Status.Allowed, sar.Status.Denied, sar.Status.Reason) + + return sar.Status.Allowed } -func channelCheck(channelName string, channelList []operatorsv1.PackageChannel) (found bool) { +func channelCheck(channelName string, channelList []operatorsv1.PackageChannel) bool { for _, channel := range channelList { if channelName == channel.Name { return true @@ -188,6 +261,51 @@ func channelCheck(channelName string, channelList []operatorsv1.PackageChannel) return false } +func findCatalogFromFallbackChannels(fallbackChannels []string, fallBackChannelAndCatalogMapping map[string][]CatalogSource) (string, []CatalogSource) { + var fallbackChannel string + var fallbackCatalog []CatalogSource + + // sort fallback channels by semantic version + semverlList, semVerChannelMappings := prunedSemverChannel(fallbackChannels) + + maxChannel := util.FindMaxSemver("", semverlList, semVerChannelMappings) + if catalogSources, ok := fallBackChannelAndCatalogMapping[maxChannel]; ok { + fallbackChannel = maxChannel + fallbackCatalog = append(fallbackCatalog, catalogSources...) + } + return fallbackChannel, fallbackCatalog +} + +func prunedSemverChannel(fallbackChannels []string) ([]string, map[string]string) { + var semverlList []string + var semVerChannelMappings = make(map[string]string) + for _, fc := range fallbackChannels { + semVerChannelMappings[util.FindSemantic(fc)] = fc + semverlList = append(semverlList, util.FindSemantic(fc)) + } + return semverlList, semVerChannelMappings +} + +func getFirstAvailableSemverChannelFromCatalog(packageManifestList *operatorsv1.PackageManifestList, fallbackChannels []string, channel, catalogName, catalogNs string) string { + semverlList, semVerChannelMappings := prunedSemverChannel(fallbackChannels) + sort.Sort(semver.ByVersion(semverlList)) + + for _, pm := range packageManifestList.Items { + if pm.Status.CatalogSource == catalogName && pm.Status.CatalogSourceNamespace == catalogNs { + if channelCheck(channel, pm.Status.Channels) { + return channel + } + // iterate the sorted semver list in reverse order to get the first available channel + for i := len(semverlList) - 1; i >= 0; i-- { + if channelCheck(semverlList[i], pm.Status.Channels) { + return semVerChannelMappings[semverlList[i]] + } + } + } + } + return "" +} + // ListOperandRegistry lists the OperandRegistry instance with default value func (m *ODLMOperator) ListOperandRegistry(ctx context.Context, label map[string]string) (*apiv1alpha1.OperandRegistryList, error) { registryList := &apiv1alpha1.OperandRegistryList{} @@ -308,36 +426,33 @@ func (m *ODLMOperator) ListOperandRequestsByConfig(ctx context.Context, key type } // GetSubscription gets Subscription by name and package name -func (m *ODLMOperator) GetSubscription(ctx context.Context, name, namespace, packageName string) (*olmv1alpha1.Subscription, error) { - klog.V(3).Infof("Fetch Subscription: %s/%s", namespace, name) - sub := &olmv1alpha1.Subscription{} - subKey := types.NamespacedName{ - Name: name, - Namespace: namespace, - } - err := m.Client.Get(ctx, subKey, sub) - if err == nil { - return sub, nil - } else if !apierrors.IsNotFound(err) { - return nil, err - } +func (m *ODLMOperator) GetSubscription(ctx context.Context, name, operatorNs, servicesNs, packageName string) (*olmv1alpha1.Subscription, error) { + klog.V(3).Infof("Fetch Subscription %s in operatorNamespace %s and servicesNamespace %s", name, operatorNs, servicesNs) - subList := &olmv1alpha1.SubscriptionList{} - if err := m.Client.List(ctx, subList, &client.ListOptions{ - Namespace: namespace, - }); err != nil { - return nil, err + tenantScope := make(map[string]struct{}) + for _, ns := range []string{operatorNs, servicesNs} { + tenantScope[ns] = struct{}{} } var subCandidates []olmv1alpha1.Subscription - for _, sub := range subList.Items { - if sub.Spec.Package == packageName { - subCandidates = append(subCandidates, sub) + for ns := range tenantScope { + subList := &olmv1alpha1.SubscriptionList{} + if err := m.Client.List(ctx, subList, &client.ListOptions{ + Namespace: ns, + }); err != nil { + return nil, err } + + for _, sub := range subList.Items { + if sub.Name == name || sub.Spec.Package == packageName { + subCandidates = append(subCandidates, sub) + } + } + } if len(subCandidates) == 0 { - return nil, err + return nil, nil } if len(subCandidates) > 1 { @@ -347,8 +462,48 @@ func (m *ODLMOperator) GetSubscription(ctx context.Context, name, namespace, pac return &subCandidates[0], nil } +func (m *ODLMOperator) GetOpReqCM(ctx context.Context, operatorName, operatorNs, servicesNs string) (*corev1.ConfigMap, error) { + klog.V(3).Infof("Fetch tracking configmap %s in operatorNamespace %s and servicesNamespace %s", operatorName, operatorNs, servicesNs) + + tenantScope := []string{operatorNs} + if operatorNs != servicesNs { + tenantScope = append(tenantScope, servicesNs) + } + + var cmCandidates []corev1.ConfigMap + for _, ns := range tenantScope { + cmList := &corev1.ConfigMapList{} + if err := m.Client.List(ctx, cmList, &client.ListOptions{Namespace: ns}); err != nil { + return nil, err + } + + for _, cm := range cmList.Items { + if cm.Annotations != nil { + if pkg, exists := cm.Annotations["packageName"]; exists && pkg == operatorName { + cmCandidates = append(cmCandidates, cm) + } + } + } + } + + if len(cmCandidates) == 0 { + return nil, nil + } + + if len(cmCandidates) > 1 { + return nil, fmt.Errorf("there are multiple Configmaps using package %v", operatorName) + } + + return &cmCandidates[0], nil +} + // GetClusterServiceVersion gets the ClusterServiceVersion from the subscription func (m *ODLMOperator) GetClusterServiceVersion(ctx context.Context, sub *olmv1alpha1.Subscription) (*olmv1alpha1.ClusterServiceVersion, error) { + // Check if subscription is nil + if sub == nil { + klog.Error("The subscription is nil") + return nil, fmt.Errorf("the subscription is nil") + } // Check the ClusterServiceVersion status in the subscription if sub.Status.InstalledCSV == "" { klog.Warningf("The ClusterServiceVersion for Subscription %s is not ready. Will check it again", sub.Name) @@ -363,7 +518,7 @@ func (m *ODLMOperator) GetClusterServiceVersion(ctx context.Context, sub *olmv1a Name: csvName, Namespace: csvNamespace, } - if err := m.Client.Get(ctx, csvKey, csv); err != nil { + if err := m.Reader.Get(ctx, csvKey, csv); err != nil { if apierrors.IsNotFound(err) { klog.V(3).Infof("ClusterServiceVersion %s is not ready. Will check it when it is stable", sub.Name) return nil, nil @@ -375,6 +530,159 @@ func (m *ODLMOperator) GetClusterServiceVersion(ctx context.Context, sub *olmv1a return csv, nil } +// GetClusterServiceVersionList gets a list of ClusterServiceVersions from the subscription +func (m *ODLMOperator) GetClusterServiceVersionList(ctx context.Context, sub *olmv1alpha1.Subscription) ([]*olmv1alpha1.ClusterServiceVersion, error) { + // Check if subscription is nil + if sub == nil { + klog.Error("The subscription is nil") + return nil, fmt.Errorf("the subscription is nil") + } + + packageName := sub.Spec.Package + csvNamespace := sub.Namespace + labelKey := packageName + "." + csvNamespace + labelKey = util.GetFirstNCharacter(labelKey, 63) + + // Get the ClusterServiceVersion list with label operators.coreos.com/packageName.csvNamespace='' + csvList := &olmv1alpha1.ClusterServiceVersionList{} + opts := []client.ListOption{ + client.MatchingLabels{fmt.Sprintf("operators.coreos.com/%s", labelKey): ""}, + client.InNamespace(csvNamespace), + } + + if err := m.Reader.List(ctx, csvList, opts...); err != nil { + if apierrors.IsNotFound(err) || len(csvList.Items) == 0 { + klog.V(3).Infof("No ClusterServiceVersion found with label operators.coreos.com/%s", labelKey) + return nil, nil + } + return nil, errors.Wrapf(err, "failed to list ClusterServiceVersions with label operators.coreos.com/%s", labelKey) + } else if len(csvList.Items) > 1 { + klog.Warningf("Multiple ClusterServiceVersions found with label operators.coreos.com/%s", labelKey) + } + + var csvs []*olmv1alpha1.ClusterServiceVersion + for i := range csvList.Items { + klog.V(3).Infof("Get ClusterServiceVersion %s in the namespace %s", csvList.Items[i].Name, csvNamespace) + csvs = append(csvs, &csvList.Items[i]) + } + + klog.V(3).Infof("Get %v ClusterServiceVersions in the namespace %s", len(csvs), csvNamespace) + return csvs, nil +} + +// GetClusterServiceVersionList gets a list of ClusterServiceVersions from the subscription +func (m *ODLMOperator) GetClusterServiceVersionListFromPackage(ctx context.Context, name, namespace string) ([]*olmv1alpha1.ClusterServiceVersion, error) { + packageName := name + csvNamespace := namespace + + csvList := &olmv1alpha1.ClusterServiceVersionList{} + + opts := []client.ListOption{ + client.InNamespace(csvNamespace), + } + + if err := m.Reader.List(ctx, csvList, opts...); err != nil { + if apierrors.IsNotFound(err) || len(csvList.Items) == 0 { + klog.V(3).Infof("No ClusterServiceVersion found") + return nil, nil + } + return nil, errors.Wrapf(err, "failed to list ClusterServiceVersions") + } + + var csvs []*olmv1alpha1.ClusterServiceVersion + // filter csvList to find one(s) that contain packageName + for _, v := range csvList.Items { + csv := v + if csv.Annotations == nil { + continue + } + if _, ok := csv.Annotations["operatorframework.io/properties"]; !ok { + continue + } + annotation := fmt.Sprintf("\"packageName\":\"%s\"", packageName) + if !strings.Contains(csv.Annotations["operatorframework.io/properties"], annotation) { + continue + } + klog.V(3).Infof("Get ClusterServiceVersion %s in the namespace %s", csv.Name, csvNamespace) + csvs = append(csvs, &csv) + } + + klog.V(3).Infof("Get %v ClusterServiceVersions in the namespace %s", len(csvs), csvNamespace) + return csvs, nil +} + +func (m *ODLMOperator) GetDeploymentListFromPackage(ctx context.Context, name, namespace string) ([]*appsv1.Deployment, error) { + packageName := name + deploymentNamespace := namespace + + deploymentList := &appsv1.DeploymentList{} + + opts := []client.ListOption{ + client.InNamespace(deploymentNamespace), + } + + if err := m.Reader.List(ctx, deploymentList, opts...); err != nil { + if apierrors.IsNotFound(err) || len(deploymentList.Items) == 0 { + klog.V(1).Infof("No Deployment found") + return nil, nil + } + return nil, errors.Wrapf(err, "failed to list Deployments") + } + + var deployments []*appsv1.Deployment + // filter deploymentList to find one(s) that contain packageName + for _, v := range deploymentList.Items { + deployment := v + if deployment.Annotations == nil { + continue + } + // if _, ok := deployment.Annotations["operatorframework.io/properties"]; !ok { + // continue + // } + // annotation := fmt.Sprintf("\"packageName\":\"%s\"", packageName) + klog.V(1).Infof("Get Deployment %s with package name %s", deployment.Name, packageName) + if !strings.Contains(deployment.Annotations["packageName"], packageName) { + continue + } + klog.V(1).Infof("Get Deployment %s in the namespace %s", deployment.Name, deploymentNamespace) + deployments = append(deployments, &deployment) + } + + klog.V(1).Infof("Get %v / %v Deployment in the namespace %s", len(deployments), len(deploymentList.Items), deploymentNamespace) + return deployments, nil +} + +func (m *ODLMOperator) DeleteRedundantCSV(ctx context.Context, csvName, operatorNs, serviceNs, packageName string) error { + // Get the csv by its name and namespace + csv := &olmv1alpha1.ClusterServiceVersion{} + csvKey := types.NamespacedName{ + Name: csvName, + Namespace: serviceNs, + } + if err := m.Reader.Get(ctx, csvKey, csv); err != nil { + if apierrors.IsNotFound(err) { + klog.V(2).Infof("ClusterServiceVersion %s is not found", csvName) + return nil + } + return errors.Wrapf(err, "failed to get ClusterServiceVersion %s/%s", serviceNs, csvName) + } + + if csv.GetLabels() == nil { + klog.V(2).Infof("ClusterServiceVersion %s in the namespace %s has no label", csvName, serviceNs) + return nil + } + // Delete the CSV if the csv does not contain label operators.coreos.com/packageName.operatorNs='' AND does not contains label olm.copiedFrom: operatorNs + if _, ok := csv.GetLabels()[fmt.Sprintf("operators.coreos.com/%s.%s", packageName, operatorNs)]; !ok { + if _, ok := csv.Labels["olm.copiedFrom"]; !ok { + klog.Infof("Delete the redundant ClusterServiceVersion %s in the namespace %s", csvName, serviceNs) + if err := m.Client.Delete(ctx, csv); err != nil { + return errors.Wrapf(err, "failed to delete ClusterServiceVersion %s/%s", serviceNs, csvName) + } + } + } + return nil +} + // GetOperatorNamespace returns the operator namespace based on the install mode func (m *ODLMOperator) GetOperatorNamespace(installMode, namespace string) string { if installMode == apiv1alpha1.InstallModeCluster { @@ -383,6 +691,57 @@ func (m *ODLMOperator) GetOperatorNamespace(installMode, namespace string) strin return namespace } +// GetOperandFromRegistry gets the Operand from the OperandRegistry +func (m *ODLMOperator) GetOperandFromRegistry(ctx context.Context, reg *apiv1alpha1.OperandRegistry, operandName string) (*apiv1alpha1.Operator, error) { + opt := reg.GetOperator(operandName) + if opt == nil { + return nil, nil + } + + // Get excluded CatalogSource from annotation + // excluded-catalogsource: catalogsource1, catalogsource2 + var excludedCatalogSources []string + if reg.Annotations != nil && reg.Annotations["excluded-catalogsource"] != "" { + excludedCatalogSources = strings.Split(reg.Annotations["excluded-catalogsource"], ",") + } + // Get catalog used by ODLM itself by check its own subscription + labelKey := util.GetFirstNCharacter("ibm-odlm"+"."+util.GetOperatorNamespace(), 63) + opts := []client.ListOption{ + client.MatchingLabels{fmt.Sprintf("operators.coreos.com/%s", labelKey): ""}, + client.InNamespace(util.GetOperatorNamespace()), + } + odlmCatalog := "" + odlmCatalogNs := "" + odlmSubList := &olmv1alpha1.SubscriptionList{} + if err := m.Reader.List(ctx, odlmSubList, opts...); err != nil || len(odlmSubList.Items) == 0 { + klog.Warningf("No Subscription found for ibm-odlm in the namespace %s", util.GetOperatorNamespace()) + } else { + odlmCatalog = odlmSubList.Items[0].Spec.CatalogSource + odlmCatalogNs = odlmSubList.Items[0].Spec.CatalogSourceNamespace + } + + catalogSourceName, catalogSourceNs, channel, err := m.GetCatalogSourceAndChannelFromPackage(ctx, opt.SourceName, opt.SourceNamespace, opt.PackageName, opt.Namespace, opt.Channel, opt.FallbackChannels, reg.Namespace, odlmCatalog, odlmCatalogNs, excludedCatalogSources) + if err != nil { + return nil, err + } + + if catalogSourceName == "" || catalogSourceNs == "" { + klog.V(2).Infof("no catalogsource found for %v", opt.PackageName) + } + + opt.SourceName, opt.SourceNamespace, opt.Channel = catalogSourceName, catalogSourceNs, channel + + return opt, nil +} + +func (m *ODLMOperator) GetOperandFromRegistryNoOLM(ctx context.Context, reg *apiv1alpha1.OperandRegistry, operandName string) (*apiv1alpha1.Operator, error) { + opt := reg.GetOperator(operandName) + if opt == nil { + return nil, nil + } + return opt, nil +} + func (m *ODLMOperator) CheckLabel(unstruct unstructured.Unstructured, labels map[string]string) bool { for k, v := range labels { if !m.HasLabel(unstruct, k) { @@ -426,3 +785,298 @@ func (m *ODLMOperator) EnsureAnnotation(cr unstructured.Unstructured, annotation } cr.SetAnnotations(existingAnnotations) } + +func (m *ODLMOperator) ParseValueReferenceInObject(ctx context.Context, key string, object interface{}, finalObject map[string]interface{}, instanceType, instanceName, instanceNs string) error { + switch object.(type) { + case map[string]interface{}: + for subKey, value := range object.(map[string]interface{}) { + if subKey == "templatingValueFrom" { + valueRef := "" + if templateRef, ok := value.(map[string]interface{}); ok { + // convert templateRef to templatingValueRef struct + templateRefByte, err := json.Marshal(templateRef) + if err != nil { + klog.Errorf("Failed to convert templateRef to templatingValueRef struct for %s %s/%s: %v", instanceType, instanceNs, instanceName, err) + return err + } + templateRefObj := &util.TemplateValueRef{} + if err := json.Unmarshal(templateRefByte, templateRefObj); err != nil { + klog.Errorf("Failed to convert templateRef to templatingValueRef struct for %s %s/%s: %v", instanceType, instanceNs, instanceName, err) + return err + } + + // get the defaultValue from template + valueRef, err = m.GetDefaultValueFromTemplate(ctx, templateRefObj, instanceType, instanceName, instanceNs) + if err != nil { + klog.Errorf("Failed to get default value from template for %s %s/%s on field %s: %v", instanceType, instanceNs, instanceName, key, err) + } + + // get the value from the ConfigMap reference + if ref, err := m.ParseConfigMapRef(ctx, templateRefObj.ConfigMapKeyRef, instanceType, instanceName, instanceNs); err != nil { + klog.Errorf("Failed to get value reference from ConfigMap for %s %s/%s on field %s: %v", instanceType, instanceNs, instanceName, key, err) + return err + } else if ref != "" { + valueRef = ref + } + + // get the value from the secret + if ref, err := m.ParseSecretKeyRef(ctx, templateRefObj.SecretRef, instanceType, instanceName, instanceNs); err != nil { + klog.Errorf("Failed to get value reference from Secret for %s %s/%s on field %s: %v", instanceType, instanceNs, instanceName, key, err) + return err + } else if ref != "" { + valueRef = ref + } + + // get the value from the object + if ref, err := m.ParseObjectRef(ctx, templateRefObj.ObjectRef, instanceType, instanceName, instanceNs); err != nil { + klog.Errorf("Failed to get value reference from Object for %s %s/%s on field %s: %v", instanceType, instanceNs, instanceName, key, err) + return err + } else if ref != "" { + valueRef = ref + } + + if valueRef == "" && templateRefObj.Required { + return errors.Errorf("Found empty value reference from template for %s %s/%s on field %s, retry in few second", instanceType, instanceNs, instanceName, key) + } + } + // overwrite the value with the value from the reference + finalObject[key] = valueRef + } else { + if err := m.ParseValueReferenceInObject(ctx, subKey, object.(map[string]interface{})[subKey], finalObject[key].(map[string]interface{}), instanceType, instanceName, instanceNs); err != nil { + return err + } + } + } + case []interface{}: + for i := range finalObject[key].([]interface{}) { + if _, ok := finalObject[key].([]interface{})[i].(map[string]interface{}); ok { + for subKey, value := range finalObject[key].([]interface{})[i].(map[string]interface{}) { + if err := m.ParseValueReferenceInObject(ctx, subKey, value, finalObject[key].([]interface{})[i].(map[string]interface{}), instanceType, instanceName, instanceNs); err != nil { + return err + } + } + } + } + } + return nil +} + +func (m *ODLMOperator) GetDefaultValueFromTemplate(ctx context.Context, template *util.TemplateValueRef, instanceType, instanceName, instanceNs string) (string, error) { + if template == nil { + return "", nil + } + if template.Default != nil { + defaultValue := template.Default.DefaultValue + if ref, err := m.ParseConfigMapRef(ctx, template.Default.ConfigMapKeyRef, instanceType, instanceName, instanceNs); err != nil { + return "", err + } else if ref != "" { + defaultValue = ref + } + if ref, err := m.ParseSecretKeyRef(ctx, template.Default.SecretRef, instanceType, instanceName, instanceNs); err != nil { + return "", err + } else if ref != "" { + defaultValue = ref + } + if ref, err := m.ParseObjectRef(ctx, template.Default.ObjectRef, instanceType, instanceName, instanceNs); err != nil { + return "", err + } else if ref != "" { + defaultValue = ref + } + + if defaultValue == "" && template.Default.Required { + return "", errors.Errorf("Failed to get default value from template, retry in few second") + } + return defaultValue, nil + } + return "", nil +} + +func (m *ODLMOperator) ParseConfigMapRef(ctx context.Context, cm *util.ConfigMapRef, instanceType, instanceName, instanceNs string) (string, error) { + if cm == nil { + return "", nil + } + if cm.Namespace == "" { + cm.Namespace = instanceNs + } + cmData, err := m.GetValueRefFromConfigMap(ctx, instanceType, instanceName, instanceNs, cm.Name, cm.Namespace, cm.Key) + if err != nil { + klog.Errorf("Failed to get value reference from ConfigMap %s/%s with key %s: %v", cm.Namespace, cm.Name, cm.Key, err) + return "", err + } + return cmData, nil +} + +func (m *ODLMOperator) ParseSecretKeyRef(ctx context.Context, secret *util.SecretRef, instanceType, instanceName, instanceNs string) (string, error) { + if secret == nil { + return "", nil + } + if secret.Namespace == "" { + secret.Namespace = instanceNs + } + secretData, err := m.GetValueRefFromSecret(ctx, instanceType, instanceName, instanceNs, secret.Name, secret.Namespace, secret.Key) + if err != nil { + klog.Errorf("Failed to get value reference from Secret %s/%s with key %s: %v", secret.Namespace, secret.Name, secret.Key, err) + return "", err + } + return secretData, nil +} + +func (m *ODLMOperator) ParseObjectRef(ctx context.Context, obj *util.ObjectRef, instanceType, instanceName, instanceNs string) (string, error) { + if obj == nil { + return "", nil + } + if obj.Namespace == "" { + obj.Namespace = instanceNs + } + if obj.APIVersion == "" { + return "", errors.New("apiVersion is empty") + } + if obj.Kind == "" { + return "", errors.New("kind is empty") + } + // get the value from the object + objData, err := m.GetValueRefFromObject(ctx, instanceType, instanceName, instanceNs, obj.APIVersion, obj.Kind, obj.Name, obj.Namespace, obj.Path) + if err != nil { + klog.Errorf("Failed to get value reference from Object %s/%s with path %s: %v", obj.Namespace, obj.Name, obj.Path, err) + return "", err + } + return objData, nil +} + +func (m *ODLMOperator) GetValueRefFromConfigMap(ctx context.Context, instanceType, instanceName, instanceNs, cmName, cmNs, configMapKey string) (string, error) { + cm := &corev1.ConfigMap{} + if err := m.Reader.Get(ctx, types.NamespacedName{Name: cmName, Namespace: cmNs}, cm); err != nil { + if apierrors.IsNotFound(err) { + klog.V(2).Infof("Configmap %s/%s is not found", cmNs, cmName) + return "", nil + } + return "", errors.Wrapf(err, "failed to get Configmap %s/%s", cmNs, cmName) + } + + // Set the Value Reference label for the ConfigMap + util.EnsureLabelsForConfigMap(cm, map[string]string{ + constant.ODLMWatchedLabel: "true", + }) + // Set the Value Reference Annotation for the ConfigMap + util.EnsureAnnotationForConfigMap(cm, map[string]string{ + constant.ODLMReferenceAnnotation: instanceType + "." + instanceNs + "." + instanceName, + }) + // Update the ConfigMap with the Value Reference label + if err := m.Update(ctx, cm); err != nil { + return "", errors.Wrapf(err, "failed to update ConfigMap %s/%s", cm.Namespace, cm.Name) + } + klog.V(2).Infof("Set the Value Reference label for ConfigMap %s/%s", cm.Namespace, cm.Name) + + if cm.Data != nil { + if data, ok := cm.Data[configMapKey]; ok { + return data, nil + } + } + return "", nil +} + +func (m *ODLMOperator) GetValueRefFromSecret(ctx context.Context, instanceType, instanceName, instanceNs, secretName, secretNs, secretKey string) (string, error) { + secret := &corev1.Secret{} + if err := m.Reader.Get(ctx, types.NamespacedName{Name: secretName, Namespace: secretNs}, secret); err != nil { + if apierrors.IsNotFound(err) { + klog.V(3).Infof("Secret %s/%s is not found", secretNs, secretName) + return "", nil + } + return "", errors.Wrapf(err, "failed to get Secret %s/%s", secretNs, secretName) + } + + // Set the Value Reference label for the Secret + util.EnsureLabelsForSecret(secret, map[string]string{ + constant.ODLMWatchedLabel: "true", + }) + // Set the Value Reference Annotation for the Secret + util.EnsureAnnotationsForSecret(secret, map[string]string{ + constant.ODLMReferenceAnnotation: instanceType + "." + instanceNs + "." + instanceName, + }) + // Update the Secret with the Value Reference label + if err := m.Update(ctx, secret); err != nil { + return "", errors.Wrapf(err, "failed to update Secret %s/%s", secret.Namespace, secret.Name) + } + klog.V(2).Infof("Set the Value Reference label for Secret %s/%s", secret.Namespace, secret.Name) + + if secret.Data != nil { + if data, ok := secret.Data[secretKey]; ok { + return string(data), nil + } + } + return "", nil +} + +func (m *ODLMOperator) GetValueRefFromObject(ctx context.Context, instanceType, instanceName, instanceNs, objAPIVersion, objKind, objName, objNs, path string) (string, error) { + var obj unstructured.Unstructured + obj.SetAPIVersion(objAPIVersion) + obj.SetKind(objKind) + if err := m.Reader.Get(ctx, types.NamespacedName{Name: objName, Namespace: objNs}, &obj); err != nil { + if apierrors.IsNotFound(err) { + klog.V(3).Infof("%s %s/%s is not found", objKind, objNs, objName) + return "", nil + } + return "", errors.Wrapf(err, "failed to get %s %s/%s", objKind, objNs, objName) + } + + // Set the Value Reference label for the object + m.EnsureLabel(obj, map[string]string{ + constant.ODLMWatchedLabel: "true", + }) + // Set the Value Reference Annotation for the Secret + m.EnsureAnnotation(obj, map[string]string{ + constant.ODLMReferenceAnnotation: instanceType + "." + instanceNs + "." + instanceName, + }) + // Update the object with the Value Reference label + if err := m.Update(ctx, &obj); err != nil { + return "", errors.Wrapf(err, "failed to update %s %s/%s", objKind, obj.GetNamespace(), obj.GetName()) + } + klog.V(2).Infof("Set the Value Reference label for %s %s/%s", objKind, obj.GetNamespace(), obj.GetName()) + + if path == "" { + return "", nil + } + + sanitizedString, err := util.SanitizeObjectString(path, obj.Object) + if err != nil { + return "", errors.Wrapf(err, "failed to parse path %v from %s %s/%s", path, obj.GetKind(), obj.GetNamespace(), obj.GetName()) + } + + klog.V(2).Infof("Get value %s from %s %s/%s", sanitizedString, objKind, obj.GetNamespace(), obj.GetName()) + return sanitizedString, nil +} + +// ObjectIsUpdatedWithException checks if the object is updated except for the ODLMWatchedLabel and ODLMReferenceAnnotation +func (m *ODLMOperator) ObjectIsUpdatedWithException(oldObj, newObj *client.Object) bool { + oldObject := *oldObj + newObject := *newObj + + // Check if labels are the same except for ODLMWatchedLabel + oldLabels := oldObject.GetLabels() + newLabels := newObject.GetLabels() + if oldLabels != nil && newLabels != nil { + delete(oldLabels, constant.ODLMWatchedLabel) + delete(newLabels, constant.ODLMWatchedLabel) + } + if !reflect.DeepEqual(oldLabels, newLabels) { + return true + } + + // Check if annotations are the same except for ODLMReferenceAnnotation + oldAnnotations := oldObject.GetAnnotations() + newAnnotations := newObject.GetAnnotations() + if oldAnnotations != nil && newAnnotations != nil { + delete(oldAnnotations, constant.ODLMReferenceAnnotation) + delete(newAnnotations, constant.ODLMReferenceAnnotation) + } + if !reflect.DeepEqual(oldAnnotations, newAnnotations) { + return true + } + + // Check if other parts of the object are unchanged + oldObject.SetLabels(nil) + oldObject.SetAnnotations(nil) + newObject.SetLabels(nil) + newObject.SetAnnotations(nil) + return !reflect.DeepEqual(oldObject, newObject) +} diff --git a/controllers/operator/manager_test.go b/controllers/operator/manager_test.go new file mode 100644 index 00000000..d99dc98e --- /dev/null +++ b/controllers/operator/manager_test.go @@ -0,0 +1,481 @@ +// +// Copyright 2022 IBM Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package operator + +import ( + "context" + "fmt" + "reflect" + "testing" + + olmv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" + operatorsv1 "github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/apis/operators/v1" + "github.com/stretchr/testify/mock" + authorizationv1 "k8s.io/api/authorization/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func TestChannelCheck(t *testing.T) { + channelName := "stable" + channelList := []operatorsv1.PackageChannel{ + { + Name: "alpha", + }, + { + Name: "beta", + }, + { + Name: "stable", + }, + } + + if !channelCheck(channelName, channelList) { + t.Errorf("Expected channelCheck to return true, but got false") + } + + channelName = "dev" + + if channelCheck(channelName, channelList) { + t.Errorf("Expected channelCheck to return false, but got true") + } +} + +func TestPrunedSemverChannel(t *testing.T) { + fallbackChannels := []string{"stable-v1", "alpha-v1.5", "v2.0.1", "stable-v2.5.0", "v4.0"} + expectedSemverList := []string{"v1", "v1.5", "v2.0.1", "v2.5.0", "v4.0"} + expectedSemVerChannelMappings := map[string]string{ + "v1": "stable-v1", + "v1.5": "alpha-v1.5", + "v2.0.1": "v2.0.1", + "v2.5.0": "stable-v2.5.0", + "v4.0": "v4.0", + } + + semverList, semVerChannelMappings := prunedSemverChannel(fallbackChannels) + + if len(semverList) != len(expectedSemverList) { + t.Errorf("Expected semver list length %d, but got %d", len(expectedSemverList), len(semverList)) + } + + for i, semver := range semverList { + if semver != expectedSemverList[i] { + t.Errorf("Expected semver %s at index %d, but got %s", expectedSemverList[i], i, semver) + } + } + + if len(semVerChannelMappings) != len(expectedSemVerChannelMappings) { + t.Errorf("Expected semver channel mappings length %d, but got %d", len(expectedSemVerChannelMappings), len(semVerChannelMappings)) + } + + for semver, channel := range semVerChannelMappings { + expectedChannel, ok := expectedSemVerChannelMappings[semver] + if !ok { + t.Errorf("Unexpected semver %s in semver channel mappings", semver) + } + if channel != expectedChannel { + t.Errorf("Expected channel %s for semver %s, but got %s", expectedChannel, semver, channel) + } + } +} + +func TestFindCatalogFromFallbackChannels(t *testing.T) { + fallbackChannels := []string{"stable-v1", "alpha-v1.5", "v2.0.1", "stable-v2.5.0", "v4.0"} + fallBackChannelAndCatalogMapping := map[string][]CatalogSource{ + "v1": {{Name: "catalog1", Namespace: "namespace1"}}, + "v1.5": {{Name: "catalog2", Namespace: "namespace2"}}, + "v2.0.1": {{Name: "catalog3", Namespace: "namespace3"}}, + "v2.5.0": {{Name: "catalog4", Namespace: "namespace4"}}, + "v4.0": {{Name: "catalog5", Namespace: "namespace5"}}, + } + + fallbackChannel, fallbackCatalog := findCatalogFromFallbackChannels(fallbackChannels, fallBackChannelAndCatalogMapping) + + expectedFallbackChannel := "v4.0" + expectedFallbackCatalog := []CatalogSource{{Name: "catalog5", Namespace: "namespace5"}} + + if fallbackChannel != expectedFallbackChannel { + t.Errorf("Expected fallback channel %s, but got %s", expectedFallbackChannel, fallbackChannel) + } + + if !reflect.DeepEqual(fallbackCatalog, expectedFallbackCatalog) { + t.Errorf("Expected fallback catalog %v, but got %v", expectedFallbackCatalog, fallbackCatalog) + } + + // Find empty catalog + fallBackChannelAndCatalogMapping = map[string][]CatalogSource{} + + fallbackChannel, fallbackCatalog = findCatalogFromFallbackChannels(fallbackChannels, fallBackChannelAndCatalogMapping) + + expectedFallbackChannel = "" + expectedFallbackCatalog = nil + + if fallbackChannel != expectedFallbackChannel { + t.Errorf("Expected fallback channel %s, but got %s", expectedFallbackChannel, fallbackChannel) + } + + if !reflect.DeepEqual(fallbackCatalog, expectedFallbackCatalog) { + t.Errorf("Expected fallback catalog %v, but got %v", expectedFallbackCatalog, fallbackCatalog) + } +} + +func TestGetFirstAvailableSemverChannelFromCatalog(t *testing.T) { + packageManifestList := &operatorsv1.PackageManifestList{ + Items: []operatorsv1.PackageManifest{ + { + Status: operatorsv1.PackageManifestStatus{ + CatalogSource: "catalog1", + CatalogSourceNamespace: "namespace1", + Channels: []operatorsv1.PackageChannel{ + { + Name: "v1.0", + }, + { + Name: "v2.0", + }, + }, + }, + }, + { + Status: operatorsv1.PackageManifestStatus{ + CatalogSource: "catalog2", + CatalogSourceNamespace: "namespace2", + Channels: []operatorsv1.PackageChannel{ + { + Name: "v1.0", + }, + { + Name: "v2.0", + }, + { + Name: "v3.0", + }, + }, + }, + }, + }, + } + + fallbackChannels := []string{} + channel := "v1.0" + + catalogName := "catalog1" + catalogNs := "namespace1" + + // Test with empty fallback channels and channel exists in the catalog + result := getFirstAvailableSemverChannelFromCatalog(packageManifestList, fallbackChannels, channel, catalogName, catalogNs) + expectedResult := "v1.0" + + if result != expectedResult { + t.Errorf("Expected result to be %s, but got %s", expectedResult, result) + } + + // Test with empty fallback channels and channel does not exist in the catalog + channel = "alpha" + result = getFirstAvailableSemverChannelFromCatalog(packageManifestList, fallbackChannels, channel, catalogName, catalogNs) + expectedResult = "" + + if result != expectedResult { + t.Errorf("Expected result to be %s, but got %s", expectedResult, result) + } + + fallbackChannels = []string{"v1.0", "v2.0"} + channel = "v3.0" + + // Test with fallback channels and channel does not exist in the catalog, but fallback channel exists + result = getFirstAvailableSemverChannelFromCatalog(packageManifestList, fallbackChannels, channel, catalogName, catalogNs) + expectedResult = "v2.0" + + if result != expectedResult { + t.Errorf("Expected result to be %s, but got %s", expectedResult, result) + } + + catalogName = "catalog2" + catalogNs = "namespace2" + channel = "v3.0" + + // Test with fallback channels, but channel exist in the catalog + result = getFirstAvailableSemverChannelFromCatalog(packageManifestList, fallbackChannels, channel, catalogName, catalogNs) + expectedResult = "v3.0" + + if result != expectedResult { + t.Errorf("Expected result to be %s, but got %s", expectedResult, result) + } + +} + +type MockReader struct { + PackageManifestList *operatorsv1.PackageManifestList + CatalogSourceList *olmv1alpha1.CatalogSourceList +} + +func (m *MockReader) Get(ctx context.Context, key client.ObjectKey, obj client.Object) error { + if catalogSource, ok := obj.(*olmv1alpha1.CatalogSource); ok { + if m.CatalogSourceList != nil { + for _, cs := range m.CatalogSourceList.Items { + if cs.Name == key.Name && cs.Namespace == key.Namespace { + *catalogSource = cs + return nil + } + } + } + return client.IgnoreNotFound(nil) + } + return nil +} + +func (m *MockReader) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error { + if packageManifestList, ok := list.(*operatorsv1.PackageManifestList); ok { + packageManifestList.Items = m.PackageManifestList.Items + } + return nil +} + +func TestGetCatalogSourceAndChannelFromPackage(t *testing.T) { + ctx := context.TODO() + + packageManifestList := &operatorsv1.PackageManifestList{ + Items: []operatorsv1.PackageManifest{ + { + Status: operatorsv1.PackageManifestStatus{ + CatalogSource: "catalog1", + CatalogSourceNamespace: "namespace1", + Channels: []operatorsv1.PackageChannel{ + { + Name: "v3.0", + }, + { + Name: "v2.0", + }, + }, + }, + }, + { + Status: operatorsv1.PackageManifestStatus{ + CatalogSource: "catalog2", + CatalogSourceNamespace: "namespace1", + Channels: []operatorsv1.PackageChannel{ + { + Name: "v1.0", + }, + { + Name: "v2.0", + }, + }, + }, + }, + }, + } + CatalogSourceList := &olmv1alpha1.CatalogSourceList{ + Items: []olmv1alpha1.CatalogSource{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "catalog1", + Namespace: "namespace1", + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "catalog2", + Namespace: "namespace1", + }, + Spec: olmv1alpha1.CatalogSourceSpec{ + Priority: 100, + }, + }, + }, + } + + fakeReader := &MockReader{ + PackageManifestList: packageManifestList, + CatalogSourceList: CatalogSourceList, + } + + mockClient := &MockClient{} + + operator := &ODLMOperator{ + Reader: fakeReader, + Client: mockClient, + } + + mockClient.mock.On("Create", ctx, mock.Anything, mock.Anything).Return(nil).Run(func(args mock.Arguments) { + sar := args.Get(1).(*authorizationv1.SelfSubjectAccessReview) + sar.Status.Allowed = true + }) + + registryNs := "registry-namespace" + odlmCatalog := "odlm-catalog" + odlmCatalogNs := "odlm-namespace" + excludedCatalogSources := []string{"excluded1", "excluded2"} + + // Test with setting catalog source explicitly + opregCatalog := "catalog1" + opregCatalogNs := "namespace1" + packageName := "package1" + namespace := "namespace1" + + // Test with channel exists in the catalog + channel := "v3.0" + fallbackChannels := []string{"v2.0", "v1.0"} + + if catalogSourceName, catalogSourceNs, availableChannel, err := operator.GetCatalogSourceAndChannelFromPackage(ctx, opregCatalog, opregCatalogNs, packageName, namespace, channel, fallbackChannels, registryNs, odlmCatalog, odlmCatalogNs, excludedCatalogSources); err != nil { + t.Errorf("Unexpected error: %v", err) + } else { + assertCatalogSourceAndChannel(t, catalogSourceName, "catalog1", catalogSourceNs, "namespace1", availableChannel, "v3.0") + } + + // Test with channel does not exist in the catalog, but fallback channel exists + channel = "v4.0" + if catalogSourceName, catalogSourceNs, availableChannel, err := operator.GetCatalogSourceAndChannelFromPackage(ctx, opregCatalog, opregCatalogNs, packageName, namespace, channel, fallbackChannels, registryNs, odlmCatalog, odlmCatalogNs, excludedCatalogSources); err != nil { + t.Errorf("Unexpected error: %v", err) + } else { + assertCatalogSourceAndChannel(t, catalogSourceName, "catalog1", catalogSourceNs, "namespace1", availableChannel, "v2.0") + } + + // Test with not setting catalog source explicitly + opregCatalog = "" + opregCatalogNs = "" + + // Test with excluded catalog sources + excludedCatalogSources = []string{"catalog1", "catalog2"} + + if catalogSourceName, catalogSourceNs, availableChannel, err := operator.GetCatalogSourceAndChannelFromPackage(ctx, opregCatalog, opregCatalogNs, packageName, namespace, channel, fallbackChannels, registryNs, odlmCatalog, odlmCatalogNs, excludedCatalogSources); err != nil { + t.Errorf("Unexpected error: %v", err) + } else { + assertCatalogSourceAndChannel(t, catalogSourceName, "", catalogSourceNs, "", availableChannel, "") + } + + excludedCatalogSources = []string{} + + // Test with channel does not exist in the catalog, and fallback channel does not exist + fallbackChannels = []string{"v4.0", "v5.0"} + channel = "v6.0" + + if catalogSourceName, catalogSourceNs, availableChannel, err := operator.GetCatalogSourceAndChannelFromPackage(ctx, opregCatalog, opregCatalogNs, packageName, namespace, channel, fallbackChannels, registryNs, odlmCatalog, odlmCatalogNs, excludedCatalogSources); err != nil { + t.Errorf("Unexpected error: %v", err) + } else { + assertCatalogSourceAndChannel(t, catalogSourceName, "", catalogSourceNs, "", availableChannel, "") + } + + // Test with channel does not exist in the catalog, and fallback channel exists + fallbackChannels = []string{"v3.0", "v2.0"} + channel = "v4.0" + + if catalogSourceName, catalogSourceNs, availableChannel, err := operator.GetCatalogSourceAndChannelFromPackage(ctx, opregCatalog, opregCatalogNs, packageName, namespace, channel, fallbackChannels, registryNs, odlmCatalog, odlmCatalogNs, excludedCatalogSources); err != nil { + t.Errorf("Unexpected error: %v", err) + } else { + assertCatalogSourceAndChannel(t, catalogSourceName, "catalog1", catalogSourceNs, "namespace1", availableChannel, "v3.0") + } + + // Test with channel does not exist in the catalog, and fallback channel exists, and found the catalog with higher priority + fallbackChannels = []string{"v2.0", "v1.0"} + channel = "v4.0" + + if catalogSourceName, catalogSourceNs, availableChannel, err := operator.GetCatalogSourceAndChannelFromPackage(ctx, opregCatalog, opregCatalogNs, packageName, namespace, channel, fallbackChannels, registryNs, odlmCatalog, odlmCatalogNs, excludedCatalogSources); err != nil { + t.Errorf("Unexpected error: %v", err) + } else { + assertCatalogSourceAndChannel(t, catalogSourceName, "catalog2", catalogSourceNs, "namespace1", availableChannel, "v2.0") + } + + // Test with channel already exist in the catalog + channel = "v1.0" + + if catalogSourceName, catalogSourceNs, availableChannel, err := operator.GetCatalogSourceAndChannelFromPackage(ctx, opregCatalog, opregCatalogNs, packageName, namespace, channel, fallbackChannels, registryNs, odlmCatalog, odlmCatalogNs, excludedCatalogSources); err != nil { + t.Errorf("Unexpected error: %v", err) + } else { + assertCatalogSourceAndChannel(t, catalogSourceName, "catalog2", catalogSourceNs, "namespace1", availableChannel, "v1.0") + } + + // Test with channel already exist in the catalog, and found the catalog with higher priority + channel = "v2.0" + + if catalogSourceName, catalogSourceNs, availableChannel, err := operator.GetCatalogSourceAndChannelFromPackage(ctx, opregCatalog, opregCatalogNs, packageName, namespace, channel, fallbackChannels, registryNs, odlmCatalog, odlmCatalogNs, excludedCatalogSources); err != nil { + t.Errorf("Unexpected error: %v", err) + } else { + assertCatalogSourceAndChannel(t, catalogSourceName, "catalog2", catalogSourceNs, "namespace1", availableChannel, "v2.0") + } + +} + +type MockClient struct { + mock mock.Mock + client.Client +} + +func (m *MockClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error { + args := m.mock.Called(ctx, obj, opts) + return args.Error(0) +} + +func TestCheckResAuth(t *testing.T) { + ctx := context.TODO() + + mockClient := &MockClient{} + operator := &ODLMOperator{ + Client: mockClient, + } + + namespace := "test-namespace" + group := "test-group" + resource := "test-resource" + verb := "get" + + // Test when SelfSubjectAccessReview is allowed + mockClient.mock.On("Create", ctx, mock.Anything, mock.Anything).Return(nil).Run(func(args mock.Arguments) { + sar := args.Get(1).(*authorizationv1.SelfSubjectAccessReview) + sar.Status.Allowed = true + }) + + if !operator.CheckResAuth(ctx, namespace, group, resource, verb) { + t.Errorf("Expected CheckResAuth to return true, but got false") + } + + // Test when SelfSubjectAccessReview is not allowed + mockClient.mock.ExpectedCalls = nil + mockClient.mock.On("Create", ctx, mock.Anything, mock.Anything).Return(nil).Run(func(args mock.Arguments) { + sar := args.Get(1).(*authorizationv1.SelfSubjectAccessReview) + sar.Status.Allowed = false + }) + + if operator.CheckResAuth(ctx, namespace, group, resource, verb) { + t.Errorf("Expected CheckResAuth to return false, but got true") + } + + // Test when Create returns an error + mockClient.mock.ExpectedCalls = nil + mockClient.mock.On("Create", ctx, mock.Anything, mock.Anything).Return(fmt.Errorf("create error")) + + if operator.CheckResAuth(ctx, namespace, group, resource, verb) { + t.Errorf("Expected CheckResAuth to return false, but got true") + } +} + +func assertCatalogSourceAndChannel(t *testing.T, catalogSourceName, expectedCatalogSourceName, catalogSourceNs, expectedCatalogSourceNs, availableChannel, expectedAvailableChannel string) { + t.Helper() + + if catalogSourceName != expectedCatalogSourceName { + t.Errorf("Expected catalog source name %s, but got %s", expectedCatalogSourceName, catalogSourceName) + } + + if catalogSourceNs != expectedCatalogSourceNs { + t.Errorf("Expected catalog source namespace %s, but got %s", expectedCatalogSourceNs, catalogSourceNs) + } + + if availableChannel != expectedAvailableChannel { + t.Errorf("Expected available channel %s, but got %s", expectedAvailableChannel, availableChannel) + } +} diff --git a/controllers/operatorchecker/operatorchecker_controller.go b/controllers/operatorchecker/operatorchecker_controller.go index 58ea7370..694bccc8 100644 --- a/controllers/operatorchecker/operatorchecker_controller.go +++ b/controllers/operatorchecker/operatorchecker_controller.go @@ -26,8 +26,8 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - "github.com/IBM/operand-deployment-lifecycle-manager/controllers/constant" - deploy "github.com/IBM/operand-deployment-lifecycle-manager/controllers/operator" + "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/constant" + deploy "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/operator" ) // Reconciler reconciles a OperatorChecker object diff --git a/controllers/operatorconfig/operatorconfig_controller.go b/controllers/operatorconfig/operatorconfig_controller.go new file mode 100644 index 00000000..29b21974 --- /dev/null +++ b/controllers/operatorconfig/operatorconfig_controller.go @@ -0,0 +1,202 @@ +// +// Copyright 2022 IBM Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package operatorconfig + +import ( + "context" + + "github.com/barkimedes/go-deepcopy" + olmv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" + "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/types" + "k8s.io/klog" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/controller-runtime/pkg/source" + + operatorv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/v4/api/v1alpha1" + "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/constant" + deploy "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/operator" +) + +// OperatorConfigReconciler reconciles a OperatorConfig object +type Reconciler struct { + *deploy.ODLMOperator +} + +//+kubebuilder:rbac:groups=operator.ibm.com,namespace="placeholder",resources=operatorconfigs;operatorconfigs/status;operatorconfigs/finalizers,verbs=get;list;watch;create;update;patch;delete + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// TODO(user): Modify the Reconcile function to compare the state specified by +// the OperatorConfig object against the actual cluster state, and then +// perform operations to make the cluster state reflect the state specified by +// the user. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.14.1/pkg/reconcile +func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + _ = log.FromContext(ctx) + + instance := &operatorv1alpha1.OperandRequest{} + if err := r.Client.Get(ctx, req.NamespacedName, instance); err != nil { + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + klog.Infof("Reconciling OperatorConfig for OperandRequest: %s/%s", instance.Namespace, instance.Name) + + for _, v := range instance.Spec.Requests { + reqBlock := v + registry, err := r.GetOperandRegistry(ctx, instance.GetRegistryKey(reqBlock)) + if err != nil { + return ctrl.Result{}, err + } + for _, u := range reqBlock.Operands { + operand := u + operator, err := r.GetOperandFromRegistry(ctx, registry, operand.Name) + if err != nil { + return ctrl.Result{}, err + } else if operator == nil || operator.OperatorConfig == "" { + continue + } + + var sub *olmv1alpha1.Subscription + sub, err = r.GetSubscription(ctx, operator.Name, operator.Namespace, registry.Namespace, operator.PackageName) + if err != nil { + return ctrl.Result{}, err + } else if sub == nil { + klog.Infof("Subscription for Operator %s/%s not found", operator.Name, operator.PackageName) + return ctrl.Result{RequeueAfter: constant.DefaultRequeueDuration}, nil + } + + var csv *olmv1alpha1.ClusterServiceVersion + csv, err = r.GetClusterServiceVersion(ctx, sub) + if err != nil { + return ctrl.Result{}, err + } else if csv == nil { + klog.Infof("ClusterServiceVersion for Operator %s/%s not found", operator.Name, operator.PackageName) + return ctrl.Result{RequeueAfter: constant.DefaultRequeueDuration}, nil + } + + klog.Infof("Fetching OperatorConfig: %s", operator.OperatorConfig) + config := &operatorv1alpha1.OperatorConfig{} + if err := r.Client.Get(ctx, types.NamespacedName{ + Name: operator.OperatorConfig, + Namespace: registry.Namespace, + }, config); err != nil { + if client.IgnoreNotFound(err) != nil { + return ctrl.Result{}, err + } + klog.Infof("OperatorConfig %s/%s does not exist for operand %s in OperandRequest %s/%s", registry.Namespace, operator.OperatorConfig, operator.Name, instance.Namespace, instance.Name) + continue + } + serviceConfig := config.GetConfigForOperator(operator.Name) + if serviceConfig == nil { + klog.Infof("OperatorConfig %s does not have configuration for operator: %s", operator.OperatorConfig, operator.Name) + continue + } + + copyToCast, err := deepcopy.Anything(csv) + if err != nil { + return ctrl.Result{}, err + } + csvToUpdate := copyToCast.(*olmv1alpha1.ClusterServiceVersion) + klog.Infof("Applying OperatorConfig: %s to Operator: %s via CSV: %s, %s", operator.OperatorConfig, operator.Name, csv.Name, csv.Namespace) + if err := r.configCsv(ctx, csvToUpdate, serviceConfig); err != nil { + klog.Errorf("Failed to apply OperatorConfig %s/%s to Operator: %s via CSV: %s, %s", registry.Namespace, operator.OperatorConfig, operator.Name, csv.Namespace, csv.Name) + return ctrl.Result{}, err + } + } + } + klog.Infof("Finished reconciling OperatorConfig for OperandRequest %s/%s", instance.Namespace, instance.Name) + return ctrl.Result{RequeueAfter: constant.DefaultSyncPeriod}, nil +} + +func (r *Reconciler) configCsv(ctx context.Context, csv *olmv1alpha1.ClusterServiceVersion, config *operatorv1alpha1.ServiceOperatorConfig) error { + if config.Replicas != nil { + csv.Spec.InstallStrategy.StrategySpec.DeploymentSpecs[0].Spec.Replicas = config.Replicas + } + if config.Affinity != nil { + csv.Spec.InstallStrategy.StrategySpec.DeploymentSpecs[0].Spec.Template.Spec.Affinity = config.Affinity + } + if config.TopologySpreadConstraints != nil { + csv.Spec.InstallStrategy.StrategySpec.DeploymentSpecs[0].Spec.Template.Spec.TopologySpreadConstraints = config.TopologySpreadConstraints + } + if err := r.Client.Update(ctx, csv); err != nil { + return err + } + return nil +} + +func (r *Reconciler) requestsFromMapFunc(ctx context.Context) handler.MapFunc { + return func(object client.Object) []reconcile.Request { + requests := []reconcile.Request{} + + operandRequests, _ := r.ListOperandRequests(ctx, nil) + for _, req := range operandRequests.Items { + r := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: req.Namespace, + Name: req.Name, + }, + } + requests = append(requests, r) + } + return requests + } +} + +// SetupWithManager sets up the controller with the Manager. +func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error { + ctx := context.Background() + return ctrl.NewControllerManagedBy(mgr). + For(&operatorv1alpha1.OperandRequest{}, builder.WithPredicates(predicate.Funcs{ + CreateFunc: func(e event.CreateEvent) bool { + return true + }, + DeleteFunc: func(e event.DeleteEvent) bool { + // Evaluates to false if the object has been confirmed deleted. + return !e.DeleteStateUnknown + }, + UpdateFunc: func(e event.UpdateEvent) bool { + oldObject := e.ObjectOld.(*operatorv1alpha1.OperandRequest) + newObject := e.ObjectNew.(*operatorv1alpha1.OperandRequest) + return !equality.Semantic.DeepEqual(oldObject.Spec, newObject.Spec) || !equality.Semantic.DeepEqual(oldObject.Status, newObject.Status) + }, + })). + Watches(&source.Kind{Type: &operatorv1alpha1.OperatorConfig{}}, handler.EnqueueRequestsFromMapFunc(r.requestsFromMapFunc(ctx)), builder.WithPredicates(predicate.Funcs{ + CreateFunc: func(e event.CreateEvent) bool { + return true + }, + DeleteFunc: func(e event.DeleteEvent) bool { + // Evaluates to false if the object has been confirmed deleted. + return !e.DeleteStateUnknown + }, + UpdateFunc: func(e event.UpdateEvent) bool { + oldObject := e.ObjectOld.(*operatorv1alpha1.OperatorConfig) + newObject := e.ObjectNew.(*operatorv1alpha1.OperatorConfig) + return !equality.Semantic.DeepEqual(oldObject.Spec, newObject.Spec) + }, + })). + Complete(r) +} diff --git a/controllers/operatorconfig/operatorconfig_suite_test.go b/controllers/operatorconfig/operatorconfig_suite_test.go new file mode 100644 index 00000000..924627bf --- /dev/null +++ b/controllers/operatorconfig/operatorconfig_suite_test.go @@ -0,0 +1,83 @@ +// +// Copyright 2022 IBM Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package operatorconfig + +import ( + "path/filepath" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + operatorsv1 "github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/apis/operators/v1" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + + operatorv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/v4/api/v1alpha1" + //+kubebuilder:scaffold:imports +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + +var cfg *rest.Config +var k8sClient client.Client +var testEnv *envtest.Environment + +func TestAPIs(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecs(t, "Controller Suite") +} + +var _ = BeforeSuite(func() { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, + ErrorIfCRDPathMissing: true, + } + + var err error + // cfg is defined in this file globally. + cfg, err = testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + err = operatorv1alpha1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = operatorsv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + + //+kubebuilder:scaffold:scheme + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + +}) + +var _ = AfterSuite(func() { + By("tearing down the test environment") + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +}) diff --git a/controllers/testutil/packagemanifests_crd.yaml b/controllers/testutil/packagemanifests_crd.yaml new file mode 100644 index 00000000..4931e9b1 --- /dev/null +++ b/controllers/testutil/packagemanifests_crd.yaml @@ -0,0 +1,164 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: packagemanifests.packages.operators.coreos.com +spec: + group: packages.operators.coreos.com + versions: + - name: v1 + served: true + storage: true + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + status: + type: object + properties: + catalogSource: + type: string + catalogSourceDisplayName: + type: string + catalogSourcePublisher: + type: string + catalogSourceNamespace: + type: string + provider: + type: object + properties: + name: + type: string + url: + type: string + packageName: + type: string + deprecation: + type: object + properties: + message: + type: string + channels: + type: array + items: + type: object + properties: + name: + type: string + currentCSV: + type: string + currentCSVDesc: + type: object + properties: + displayName: + type: string + icon: + type: array + items: + type: object + properties: + base64data: + type: string + mediatype: + type: string + version: + type: object + properties: + version: + type: string + provider: + type: object + properties: + name: + type: string + url: + type: string + annotations: + type: object + additionalProperties: + type: string + keywords: + type: array + items: + type: string + links: + type: array + items: + type: object + properties: + name: + type: string + url: + type: string + maintainers: + type: array + items: + type: object + properties: + name: + type: string + email: + type: string + maturity: + type: string + longDescription: + type: string + installModes: + type: array + items: + type: object + properties: + type: + type: string + supported: + type: boolean + customResourceDefinitions: + type: object + apiServiceDefinitions: + type: object + nativeAPIs: + type: array + items: + type: object + properties: + group: + type: string + version: + type: string + kind: + type: string + minKubeVersion: + type: string + relatedImages: + type: array + items: + type: string + deprecation: + type: object + properties: + message: + type: string + entries: + type: array + items: + type: object + properties: + name: + type: string + version: + type: string + deprecation: + type: object + properties: + message: + type: string + defaultChannel: + type: string + names: + plural: packagemanifests + singular: packagemanifest + kind: PackageManifest + shortNames: + - pm + scope: Namespaced \ No newline at end of file diff --git a/controllers/testutil/test_data.go b/controllers/testutil/test_data.go index 21532643..bc87e3a2 100644 --- a/controllers/testutil/test_data.go +++ b/controllers/testutil/test_data.go @@ -25,31 +25,38 @@ const ( Interval = time.Second * 5 ) -const EtcdExample string = ` +const JaegerExample string = ` [ { - "apiVersion": "etcd.database.coreos.com/v1beta2", - "kind": "EtcdCluster", + "apiVersion": "jaegertracing.io/v1", + "kind": "Jaeger", "metadata": { - "name": "example" + "name": "my-jaeger" }, "spec": { - "size": 3, - "version": "3.2.13" + "strategy": "allinone" } } ] ` -const JenkinsExample string = ` +const MongodbExample string = ` [ { - "apiVersion": "jenkins.io/v1alpha2", - "kind": "Jenkins", + "apiVersion": "atlas.mongodb.com/v1", + "kind": "AtlasDeployment", "metadata": { - "name": "example" + "name": "my-atlas-deployment" }, "spec": { - "service": {"port": 8081} + "deploymentSpec": { + "name": "test-deployment", + "providerSettings": { + "instanceSizeName": "M10", + "providerName": "AWS", + "regionName": "US_EAST_1" + } + }, + "projectRef": {"name": "my-project"} } } ] diff --git a/controllers/testutil/test_util.go b/controllers/testutil/test_util.go index b37f588c..c79c6e2a 100644 --- a/controllers/testutil/test_util.go +++ b/controllers/testutil/test_util.go @@ -30,8 +30,8 @@ import ( nssv1 "github.com/IBM/ibm-namespace-scope-operator/api/v1" - apiv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/api/v1alpha1" - "github.com/IBM/operand-deployment-lifecycle-manager/controllers/constant" + apiv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/v4/api/v1alpha1" + "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/constant" // +kubebuilder:scaffold:imports ) @@ -56,7 +56,7 @@ var SubConfig = &olmv1alpha1.SubscriptionConfig{ // CreateNSName generates random namespace names. Namespaces are never deleted in test environment func CreateNSName(prefix string) string { - suffix := make([]byte, 10) + suffix := make([]byte, 4) _, err := rand.Read(suffix) if err != nil { panic(err) @@ -74,21 +74,21 @@ func OperandRegistryObj(name, namespace, subNamespace string) *apiv1alpha1.Opera Spec: apiv1alpha1.OperandRegistrySpec{ Operators: []apiv1alpha1.Operator{ { - Name: "etcd", + Name: "jaeger", Namespace: subNamespace, SourceName: "community-operators", SourceNamespace: "openshift-marketplace", - PackageName: "etcd", - Channel: "singlenamespace-alpha", + PackageName: "jaeger", + Channel: "stable", Scope: "public", }, { - Name: "jenkins", + Name: "mongodb-atlas-kubernetes", Namespace: subNamespace, SourceName: "community-operators", SourceNamespace: "openshift-marketplace", - PackageName: "jenkins-operator", - Channel: "alpha", + PackageName: "mongodb-atlas-kubernetes", + Channel: "stable", Scope: "public", }, }, @@ -106,22 +106,22 @@ func OperandRegistryObjwithCfg(name, namespace, subNamespace string) *apiv1alpha Spec: apiv1alpha1.OperandRegistrySpec{ Operators: []apiv1alpha1.Operator{ { - Name: "etcd", + Name: "jaeger", Namespace: subNamespace, SourceName: "community-operators", SourceNamespace: "openshift-marketplace", - PackageName: "etcd", - Channel: "singlenamespace-alpha", + PackageName: "jaeger", + Channel: "stable", Scope: "public", SubscriptionConfig: SubConfig, }, { - Name: "jenkins", + Name: "mongodb-atlas-kubernetes", Namespace: subNamespace, SourceName: "community-operators", SourceNamespace: "openshift-marketplace", - PackageName: "jenkins-operator", - Channel: "alpha", + PackageName: "mongodb-atlas-kubernetes", + Channel: "stable", Scope: "public", }, }, @@ -139,32 +139,77 @@ func OperandConfigObj(name, namespace string) *apiv1alpha1.OperandConfig { Spec: apiv1alpha1.OperandConfigSpec{ Services: []apiv1alpha1.ConfigService{ { - Name: "etcd", - Spec: map[string]runtime.RawExtension{ - "etcdCluster": {Raw: []byte(`{"size": 3}`)}, + Name: "jaeger", + Spec: map[string]apiv1alpha1.ExtensionWithMarker{ + "jaeger": { + RawExtension: runtime.RawExtension{Raw: []byte(`{ + "strategy": { + "templatingValueFrom": { + "required": true, + "configMapKeyRef": { + "name": "jaeger-configmap-reference", + "key": "putStrategy" + } + } + } + }`), + }, + }, }, Resources: []apiv1alpha1.ConfigResource{ { - Name: "jenkins-configmap", + Name: "jaeger-configmap", APIVersion: "v1", Kind: "ConfigMap", Labels: map[string]string{ - "etcd": "etcd-configmap", + "jaeger": "jaeger-configmap", }, Annotations: map[string]string{ - "etcd": "etcd-configmap", + "jaeger": "jaeger-configmap", }, Data: &runtime.RawExtension{ - Raw: []byte(`{"data": {"size": "3"}}`), + Raw: []byte(`{"data": {"strategy": "allinone"}}`), }, Force: false, }, + { + Name: "jaeger-configmap-reference", + APIVersion: "v1", + Kind: "ConfigMap", + Labels: map[string]string{ + "jaeger": "jaeger-configmap-reference", + }, + Annotations: map[string]string{ + "jaeger": "jaeger-configmap-reference", + }, + Data: &runtime.RawExtension{ + Raw: []byte(`{ + "data": { + "getStrategy": { + "templatingValueFrom": { + "required": true, + "objectRef": { + "apiVersion": "v1", + "kind": "ConfigMap", + "name": "jaeger-configmap", + "path": "data.strategy" + } + } + }, + "putStrategy": "streaming" + } + }`), + }, + Force: true, + }, }, }, { - Name: "jenkins", - Spec: map[string]runtime.RawExtension{ - "jenkins": {Raw: []byte(`{"service":{"port": 8081}}`)}, + Name: "mongodb-atlas-kubernetes", + Spec: map[string]apiv1alpha1.ExtensionWithMarker{ + "atlasDeployment": { + RawExtension: runtime.RawExtension{Raw: []byte(`{"deploymentSpec":{"name": "test-deployment"}, "projectRef": {"name": "my-updated-project"}}`)}, + }, }, }, }, @@ -189,11 +234,11 @@ func OperandRequestObj(registryName, registryNamespace, requestName, requestName RegistryNamespace: registryNamespace, Operands: []apiv1alpha1.Operand{ { - Name: "etcd", + Name: "jaeger", }, { - Name: "jenkins", - Bindings: map[string]apiv1alpha1.SecretConfigmap{ + Name: "mongodb-atlas-kubernetes", + Bindings: map[string]apiv1alpha1.Bindable{ "public": { Secret: "secret4", Configmap: "cm4", @@ -224,29 +269,38 @@ func OperandRequestObjWithCR(registryName, registryNamespace, requestName, reque RegistryNamespace: registryNamespace, Operands: []apiv1alpha1.Operand{ { - Name: "etcd", - Kind: "EtcdCluster", - APIVersion: "etcd.database.coreos.com/v1beta2", - InstanceName: "example", + Name: "jaeger", + Kind: "Jaeger", + APIVersion: "jaegertracing.io/v1", + InstanceName: "my-jaeger", Spec: &runtime.RawExtension{ - Raw: []byte(`{"size": 3,"version": "3.2.13"}`), + Raw: []byte(`{"strategy": "allinone"}`), }, }, { - Name: "etcd", - Kind: "EtcdCluster", - APIVersion: "etcd.database.coreos.com/v1beta2", + Name: "jaeger", + Kind: "Jaeger", + APIVersion: "jaegertracing.io/v1", Spec: &runtime.RawExtension{ - Raw: []byte(`{"size": 3,"version": "3.2.15"}`), + Raw: []byte(`{"strategy": "streaming"}`), }, }, { - Name: "jenkins", - Kind: "Jenkins", - APIVersion: "jenkins.io/v1alpha2", - InstanceName: "example", + Name: "mongodb-atlas-kubernetes", + Kind: "AtlasDeployment", + APIVersion: "atlas.mongodb.com/v1", + InstanceName: "my-atlas-deployment", Spec: &runtime.RawExtension{ - Raw: []byte(`{"service": {"port": 8081}}`), + Raw: []byte(`{ + "deploymentSpec": { + "name": "test-deployment", + "providerSettings": { + "instanceSizeName": "M10", + "providerName": "AWS", + "regionName": "US_EAST_1" + } + }, + "projectRef": {"name": "my-project"}}`), }, }, }, @@ -273,11 +327,11 @@ func OperandRequestObjWithProtected(registryName, registryNamespace, requestName RegistryNamespace: registryNamespace, Operands: []apiv1alpha1.Operand{ { - Name: "etcd", + Name: "jaeger", }, { - Name: "jenkins", - Bindings: map[string]apiv1alpha1.SecretConfigmap{ + Name: "mongodb-atlas-kubernetes", + Bindings: map[string]apiv1alpha1.Bindable{ "protected": { Secret: "secret5", Configmap: "cm5", @@ -299,10 +353,10 @@ func OperandBindInfoObj(name, namespace, registryName, registryNamespace string) Namespace: namespace, }, Spec: apiv1alpha1.OperandBindInfoSpec{ - Operand: "jenkins", + Operand: "mongodb-atlas-kubernetes", Registry: registryName, RegistryNamespace: registryNamespace, - Bindings: map[string]apiv1alpha1.SecretConfigmap{ + Bindings: map[string]apiv1alpha1.Bindable{ "public": { Secret: "secret1", Configmap: "cm1", @@ -441,7 +495,7 @@ func SubscriptionStatus(name, namespace, csvVersion string) olmv1alpha1.Subscrip } } -func ClusterServiceVersion(name, namespace, example string) *olmv1alpha1.ClusterServiceVersion { +func ClusterServiceVersion(name, packageName, namespace, example string) *olmv1alpha1.ClusterServiceVersion { return &olmv1alpha1.ClusterServiceVersion{ ObjectMeta: metav1.ObjectMeta{ Name: name, @@ -449,6 +503,9 @@ func ClusterServiceVersion(name, namespace, example string) *olmv1alpha1.Cluster Annotations: map[string]string{ "alm-examples": example, }, + Labels: map[string]string{ + "operators.coreos.com/" + packageName + "." + namespace: "", + }, }, Spec: olmv1alpha1.ClusterServiceVersionSpec{ InstallStrategy: olmv1alpha1.NamedInstallStrategy{ diff --git a/controllers/util/merge.go b/controllers/util/merge.go index ab38645b..d8dc046e 100644 --- a/controllers/util/merge.go +++ b/controllers/util/merge.go @@ -18,8 +18,8 @@ package util import ( "encoding/json" - "reflect" + "k8s.io/apimachinery/pkg/api/equality" "k8s.io/klog" ) @@ -29,6 +29,7 @@ func MergeCR(defaultCR, changedCR []byte) map[string]interface{} { return make(map[string]interface{}) } + // Handle when only one CR is provided defaultCRDecoded := make(map[string]interface{}) changedCRDecoded := make(map[string]interface{}) if len(defaultCR) != 0 && len(changedCR) == 0 { @@ -52,14 +53,17 @@ func MergeCR(defaultCR, changedCR []byte) map[string]interface{} { if changedCRUnmarshalErr != nil { klog.Errorf("failed to unmarshal service spec: %v", changedCRUnmarshalErr) } + + // Merge both specs for key := range defaultCRDecoded { checkKeyBeforeMerging(key, defaultCRDecoded[key], changedCRDecoded[key], changedCRDecoded) } + return changedCRDecoded } func checkKeyBeforeMerging(key string, defaultMap interface{}, changedMap interface{}, finalMap map[string]interface{}) { - if !reflect.DeepEqual(defaultMap, changedMap) { + if !equality.Semantic.DeepEqual(defaultMap, changedMap) { switch defaultMap := defaultMap.(type) { case map[string]interface{}: //Check that the changed map value doesn't contain this map at all and is nil @@ -72,6 +76,22 @@ func checkKeyBeforeMerging(key string, defaultMap interface{}, changedMap interf checkKeyBeforeMerging(newKey, defaultMapRef[newKey], changedMapRef[newKey], finalMap[key].(map[string]interface{})) } } + case []interface{}: + if changedMap == nil { + finalMap[key] = defaultMap + } else if _, ok := changedMap.([]interface{}); ok { //Check that the changed map value is also a slice []interface + defaultMapRef := defaultMap + changedMapRef := changedMap.([]interface{}) + for i := range defaultMapRef { + if _, ok := defaultMapRef[i].(map[string]interface{}); ok { + if len(changedMapRef) > i { + for newKey := range defaultMapRef[i].(map[string]interface{}) { + checkKeyBeforeMerging(newKey, defaultMapRef[i].(map[string]interface{})[newKey], changedMapRef[i].(map[string]interface{})[newKey], finalMap[key].([]interface{})[i].(map[string]interface{})) + } + } + } + } + } default: //Check if the value was set, otherwise set it if changedMap == nil { diff --git a/controllers/util/util.go b/controllers/util/util.go index ad53be14..2aea936f 100644 --- a/controllers/util/util.go +++ b/controllers/util/util.go @@ -17,16 +17,68 @@ package util import ( + "bytes" + "crypto/sha256" + "encoding/hex" + "fmt" "os" + "regexp" "sort" "strconv" "strings" "sync" "time" + ocproute "github.com/openshift/api/route/v1" + "golang.org/x/mod/semver" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/discovery" + "k8s.io/client-go/util/jsonpath" + + constant "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/constant" ) +type TemplateValueRef struct { + Required bool `json:"required,omitempty"` + Default *DefaultObjectRef `json:"default,omitempty"` + ConfigMapKeyRef *ConfigMapRef `json:"configMapKeyRef,omitempty"` + SecretRef *SecretRef `json:"secretKeyRef,omitempty"` + // RouteRef *RouteRef `json:"routePathRef,omitempty"` + ObjectRef *ObjectRef `json:"objectRef,omitempty"` +} + +type DefaultObjectRef struct { + Required bool `json:"required,omitempty"` + ConfigMapKeyRef *ConfigMapRef `json:"configMapKeyRef,omitempty"` + SecretRef *SecretRef `json:"secretKeyRef,omitempty"` + // RouteRef *RouteRef `json:"routePathRef,omitempty"` + ObjectRef *ObjectRef `json:"objectRef,omitempty"` + DefaultValue string `json:"defaultValue,omitempty"` +} + +type ConfigMapRef struct { + Name string `json:"name"` + Namespace string `json:"namespace"` + Key string `json:"key"` +} + +type SecretRef struct { + Name string `json:"name"` + Namespace string `json:"namespace"` + Key string `json:"key"` +} + +type ObjectRef struct { + Name string `json:"name"` + Namespace string `json:"namespace"` + Path string `json:"path"` + APIVersion string `json:"apiVersion"` + Kind string `json:"kind"` +} + // GetOperatorNamespace returns the Namespace of the operator func GetOperatorNamespace() string { ns, found := os.LookupEnv("OPERATOR_NAMESPACE") @@ -45,6 +97,15 @@ func GetWatchNamespace() string { return ns } +// GetNoOLM returns boolean NoOLM enabled +func GetNoOLM() string { + enabled, found := os.LookupEnv("NO_OLM") + if !found { + return "false" + } + return enabled +} + // GetInstallScope returns the scope of the installation func GetInstallScope() string { ns, found := os.LookupEnv("INSTALL_SCOPE") @@ -104,6 +165,39 @@ func StringSliceContentEqual(a, b []string) bool { return true } +// CalculateHash calculates the hash value for single resource +func CalculateHash(input []byte) string { + if len(input) == 0 { + return "" + } + hashedData := sha256.Sum256(input) + return hex.EncodeToString(hashedData[:7]) +} + +// CalculateResHashes calculates the hash for the existing cluster resource and the new template resource +func CalculateResHashes(fromCluster *unstructured.Unstructured, fromTemplate []byte) (string, string) { + templateHash := CalculateHash(fromTemplate) + + if fromCluster != nil { + clusterAnnos := fromCluster.GetAnnotations() + clusterHash := "" + if clusterAnnos != nil { + clusterHash = clusterAnnos[constant.K8sHashedData] + } + return clusterHash, templateHash + } + return "", templateHash +} + +// SetHashAnnotation sets the hash annotation in the object +func AddHashAnnotation(obj *unstructured.Unstructured, key, hash string, newAnnotations map[string]string) map[string]string { + if newAnnotations == nil { + newAnnotations = make(map[string]string) + } + newAnnotations[key] = hash + return newAnnotations +} + // WaitTimeout waits for the waitgroup for the specified max timeout. // Returns true if waiting timed out. func WaitTimeout(wg *sync.WaitGroup, timeout time.Duration) bool { @@ -188,3 +282,280 @@ func Contains(list []string, s string) bool { } return false } + +func Differs(list []string, s string) bool { + for _, v := range list { + if v != s { + return true + } + } + return false +} + +func ObjectToNewUnstructured(obj interface{}) (*unstructured.Unstructured, error) { + content, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj) + if err != nil { + return nil, fmt.Errorf("could not convert Object to Unstructured resource: %v", err) + } + newUnstr := &unstructured.Unstructured{} + newUnstr.SetUnstructuredContent(content) + return newUnstr, nil +} + +func EnsureLabelsForSecret(secret *corev1.Secret, labels map[string]string) { + if secret.Labels == nil { + secret.Labels = make(map[string]string) + } + for k, v := range labels { + secret.Labels[k] = v + } +} + +func EnsureAnnotationsForSecret(secret *corev1.Secret, annotatinos map[string]string) { + if secret.Annotations == nil { + secret.Annotations = make(map[string]string) + } + for k, v := range annotatinos { + secret.Annotations[k] = v + } +} + +func EnsureLabelsForConfigMap(cm *corev1.ConfigMap, labels map[string]string) { + if cm.Labels == nil { + cm.Labels = make(map[string]string) + } + for k, v := range labels { + cm.Labels[k] = v + } +} + +func EnsureAnnotationForConfigMap(cm *corev1.ConfigMap, annotations map[string]string) { + if cm.Annotations == nil { + cm.Annotations = make(map[string]string) + } + for k, v := range annotations { + cm.Annotations[k] = v + } +} + +func EnsureLabelsForRoute(r *ocproute.Route, labels map[string]string) { + if r.Labels == nil { + r.Labels = make(map[string]string) + } + for k, v := range labels { + r.Labels[k] = v + } +} + +func EnsureLabelsForService(s *corev1.Service, labels map[string]string) { + if s.Labels == nil { + s.Labels = make(map[string]string) + } + for k, v := range labels { + s.Labels[k] = v + } +} + +func CompareSecret(secret *corev1.Secret, existingSecret *corev1.Secret) (needUpdate bool) { + return !equality.Semantic.DeepEqual(secret.GetLabels(), existingSecret.GetLabels()) || + !equality.Semantic.DeepEqual(secret.Type, existingSecret.Type) || + !equality.Semantic.DeepEqual(secret.Data, existingSecret.Data) || + !equality.Semantic.DeepEqual(secret.StringData, existingSecret.StringData) || + !equality.Semantic.DeepEqual(secret.GetOwnerReferences(), existingSecret.GetOwnerReferences()) +} + +func CompareConfigMap(configMap *corev1.ConfigMap, existingConfigMap *corev1.ConfigMap) (needUpdate bool) { + return !equality.Semantic.DeepEqual(configMap.GetLabels(), existingConfigMap.GetLabels()) || + !equality.Semantic.DeepEqual(configMap.Data, existingConfigMap.Data) || + !equality.Semantic.DeepEqual(configMap.BinaryData, existingConfigMap.BinaryData) || + !equality.Semantic.DeepEqual(configMap.GetOwnerReferences(), existingConfigMap.GetOwnerReferences()) +} + +// SanitizeObjectString takes a string, i.e. .metadata.namespace, and a K8s object +// and returns a string got from K8s object. The required string +// is sanitized because the values are YAML fields in a K8s object. +// Ensures that: +// 1. the field actually exists, otherwise returns an error +// 2. extracts the value from the K8s Service's field, the value will be +// stringified +func SanitizeObjectString(jsonPath string, data interface{}) (string, error) { + jpath := jsonpath.New("sanitizeObjectData") + stringParts := strings.Split(jsonPath, "+") + sanitized := "" + for _, s := range stringParts { + actual := s + if strings.HasPrefix(s, ".") { + if len(s) > 1 { + if err := jpath.Parse("{" + s + "}"); err != nil { + return "", err + } + buf := new(bytes.Buffer) + if err := jpath.Execute(buf, data); err != nil { + return "", err + } + actual = buf.String() + } + } + sanitized += actual + } + return sanitized, nil +} + +// FindSemantic checks if a given string contains a substring which is a valid semantic version, and returns that substring +func FindSemantic(input string) string { + // Define the regular expression pattern for flexible semantic versions + semverPattern := `\bv?(\d+)(\.\d+)?(\.\d+)?\b` + + // Compile the regular expression + re := regexp.MustCompile(semverPattern) + + // Find the first match in the input string + match := re.FindString(input) + + // if no match is found, return default minimal version + if match == "" { + return "v0.0.0" + } + + return match +} + +// FindMinSemver returns the minimal semantic version by given channel and semver list +func FindMinSemver(curChannel string, semverlList []string, semVerChannelMappings map[string]string) string { + if len(semverlList) == 0 { + return "" + } else if !Contains(semverlList, FindSemantic(curChannel)) || curChannel == "" { // if current channel is not in the list or empty + // change channel to minimal version in the list + sort.Sort(semver.ByVersion(semverlList)) + return semVerChannelMappings[semverlList[0]] + } + return curChannel +} + +// FindMaxSemver returns the maximal semantic version by given channel and semver list +func FindMaxSemver(curChannel string, semverlList []string, semVerChannelMappings map[string]string) string { + if len(semverlList) == 0 { + return "" + } else if !Contains(semverlList, FindSemantic(curChannel)) || curChannel == "" { // if current channel is not in the list or empty + // change channel to maximal version in the list + sort.Sort(semver.ByVersion(semverlList)) + return semVerChannelMappings[semverlList[len(semverlList)-1]] + } + return curChannel +} + +func FindSemverFromAnnotations(annotations map[string]string) ([]string, map[string]string) { + var semverlList []string + var semVerChannelMappings = make(map[string]string) + reg, _ := regexp.Compile(`^(.*)\.(.*)\.(.*)\/request`) + for anno, channel := range annotations { + prunedChannel := FindSemantic(channel) + if reg.MatchString(anno) && semver.IsValid(prunedChannel) { + semverlList = append(semverlList, prunedChannel) + semVerChannelMappings[prunedChannel] = channel + } + } + return semverlList, semVerChannelMappings +} + +func FindMinSemverFromAnnotations(annotations map[string]string, curChannel string) string { + // check request annotation in subscription, get all available channels + semverlList, semVerChannelMappings := FindSemverFromAnnotations(annotations) + return FindMinSemver(curChannel, semverlList, semVerChannelMappings) +} + +// RemoveObjectField removes the field from the object according to the jsonPath +// jsonPath is a string that represents the path to the field in the object, always starts with "." +func RemoveObjectField(obj interface{}, jsonPath string) { + // Remove the first dot in the beginning of the jsonPath + jsonPath = strings.TrimPrefix(jsonPath, ".") + fields := strings.Split(jsonPath, ".") + + // Check if the object is a map + if objMap, ok := obj.(map[string]interface{}); ok { + removeField(objMap, fields) + } +} + +// removeField removes the field from the object according to the jsonPath +func removeField(obj map[string]interface{}, fields []string) { + // Check if fields is in the format of one item in the list. For example "container[0]" + if strings.Contains(fields[0], "[") { + // Get the field name and the index + field := strings.Split(fields[0], "[")[0] + index, _ := strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(fields[0], field+"["), "]")) + // Check if the field is a list + if _, ok := obj[field].([]interface{}); !ok { + return + } + // Check if the index is out of range + if index >= len(obj[field].([]interface{})) { + return + } + // Remove the value from the list + removeField(obj[field].([]interface{})[index].(map[string]interface{}), fields[1:]) + return + } + // Check if the field is the last field in the list + if len(fields) == 1 { + delete(obj, fields[0]) + return + } + // Check if the field is a map + if _, ok := obj[fields[0]].(map[string]interface{}); !ok { + return + } + // Remove the value from the map + removeField(obj[fields[0]].(map[string]interface{}), fields[1:]) +} + +// AddObjectField adds the field to the object according to the jsonPath +// jsonPath is a string that represents the path to the field in the object, always starts with "." +func AddObjectField(obj interface{}, jsonPath string, value interface{}) { + // Remove the first dot in the beginning of the jsonPath if it exists + jsonPath = strings.TrimPrefix(jsonPath, ".") + fields := strings.Split(jsonPath, ".") + + // Check if the object is a map + if objMap, ok := obj.(map[string]interface{}); ok { + addField(objMap, fields, value) + } +} + +func addField(obj map[string]interface{}, fields []string, value interface{}) { + // Check if fields is in the format of one item in the list. For example "container[0]" + if strings.Contains(fields[0], "[") { + // Get the field name and the index + field := strings.Split(fields[0], "[")[0] + index, _ := strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(fields[0], field+"["), "]")) + // Check if the field is a list + if _, ok := obj[field].([]interface{}); !ok { + obj[field] = make([]interface{}, 0) + } + // Check if the index is out of range + if index >= len(obj[field].([]interface{})) { + obj[field] = append(obj[field].([]interface{}), make(map[string]interface{})) + } + // Add the value to the list + addField(obj[field].([]interface{})[index].(map[string]interface{}), fields[1:], value) + return + } + // Check if the field is the last field in the list + if len(fields) == 1 { + obj[fields[0]] = value + return + } + // Check if the field is a map + if _, ok := obj[fields[0]].(map[string]interface{}); !ok { + obj[fields[0]] = make(map[string]interface{}) + } + // Add the value to the map + addField(obj[fields[0]].(map[string]interface{}), fields[1:], value) +} + +func GetFirstNCharacter(str string, n int) string { + if n >= len(str) { + return str + } + return str[:n] +} diff --git a/controllers/util/util_test.go b/controllers/util/util_test.go index 50df8ad2..9f70e6c6 100644 --- a/controllers/util/util_test.go +++ b/controllers/util/util_test.go @@ -21,6 +21,7 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) var _ = Describe("Get environmental variables", func() { @@ -71,3 +72,437 @@ var _ = Describe("Get environmental variables", func() { }) }) }) + +var _ = Describe("Contains", func() { + It("Should return true if the list contains the string", func() { + list := []string{"apple", "banana", "cherry"} + s := "banana" + Expect(Contains(list, s)).Should(BeTrue()) + }) + + It("Should return false if the list does not contain the string", func() { + list := []string{"apple", "banana", "cherry"} + s := "orange" + Expect(Contains(list, s)).Should(BeFalse()) + }) + + It("Should return false if the list is empty", func() { + list := []string{} + s := "apple" + Expect(Contains(list, s)).Should(BeFalse()) + }) +}) + +var _ = Describe("Differs", func() { + It("Should return true if the list contains a different string", func() { + list := []string{"apple", "banana", "cherry"} + s := "banana" + Expect(Differs(list, s)).Should(BeTrue()) + }) + + It("Should return false if the list contains only the same string", func() { + list := []string{"apple", "apple", "apple"} + s := "apple" + Expect(Differs(list, s)).Should(BeFalse()) + }) + + It("Should return false if the list is empty", func() { + list := []string{} + s := "apple" + Expect(Differs(list, s)).Should(BeFalse()) + }) +}) + +var _ = Describe("FindSemantic", func() { + It("Should return the semantic vX version substring", func() { + input := "stable-v1" + expected := "v1" + Expect(FindSemantic(input)).Should(Equal(expected)) + }) + + It("Should return the semantic vX.Y version substring", func() { + input := "fast-v1.2" + expected := "v1.2" + Expect(FindSemantic(input)).Should(Equal(expected)) + }) + + It("Should return the original semantic vX.Y version substring", func() { + input := "v1.2" + expected := "v1.2" + Expect(FindSemantic(input)).Should(Equal(expected)) + }) + + It("Should return the semantic vX.Y.Z version substring", func() { + input := "stable-v1.2.3" + expected := "v1.2.3" + Expect(FindSemantic(input)).Should(Equal(expected)) + }) + + It("Should return an empty string if no semantic version is found", func() { + input := "This is a test string without a semantic version" + expected := "v0.0.0" + Expect(FindSemantic(input)).Should(Equal(expected)) + }) + + It("Should return the first semantic version substring", func() { + input := "This is a test v1.2.3 string with v0.1.0 multiple semantic versions" + expected := "v1.2.3" + Expect(FindSemantic(input)).Should(Equal(expected)) + }) +}) + +var _ = Describe("FindMinSemverFromAnnotations", func() { + It("Should return the minimal semantic version from annotations", func() { + annotations := map[string]string{ + "namespace-a.common-service.operator-a/request": "stable", + "namespace-b.common-service.operator-b/request": "stable-v1.0", + "namespace-c.common-service.operator-c/request": "stable-v1.1.0", + "namespace-d.common-service.operator-d/request": "stable-v1.2.0", + } + curChannel := "stable-v1.3.0" + expected := "stable" + Expect(FindMinSemverFromAnnotations(annotations, curChannel)).Should(Equal(expected)) + }) + + It("Should return the minimal semantic version from annotations", func() { + annotations := map[string]string{ + "namespace-b.common-service.operator-b/request": "v4.0", + "namespace-c.common-service.operator-c/request": "v4.1", + "namespace-d.common-service.operator-d/request": "v4.2", + } + curChannel := "v3" + expected := "v4.0" + Expect(FindMinSemverFromAnnotations(annotations, curChannel)).Should(Equal(expected)) + }) + + It("Should return the current channel if it exists in annotations", func() { + annotations := map[string]string{ + "namespace-a.common-service.operator-a/request": "stable", + "namespace-b.common-service.operator-b/request": "stable-v1.0", + "namespace-c.common-service.operator-c/request": "stable-v1.1.0", + "namespace-d.common-service.operator-d/request": "stable-v1.2", + } + curChannel := "stable-v1.1.0" + expected := "stable-v1.1.0" + Expect(FindMinSemverFromAnnotations(annotations, curChannel)).Should(Equal(expected)) + }) + + It("Should return the current channel if it exists in annotations", func() { + annotations := map[string]string{ + "namespace-a.common-service.operator-a/request": "stable", + "namespace-b.common-service.operator-b/request": "v1.0", + "namespace-c.common-service.operator-c/request": "v2.0", + "namespace-d.common-service.operator-d/request": "v2.0", + } + curChannel := "stable" + expected := "stable" + Expect(FindMinSemverFromAnnotations(annotations, curChannel)).Should(Equal(expected)) + }) + + It("Should return an empty string if no valid semantic versions are found", func() { + annotations := map[string]string{ + "namespace-a.common-service.operator-a/config": "stable", + "namespace-b.common-service.operator-b/config": "stable-v1.0", + "namespace-c.common-service.operator-c/config": "stable-v1.1.0", + "namespace-d.common-service.operator-d/config": "stable-v1.2", + } + curChannel := "stable-v2.0.0" + expected := "" + Expect(FindMinSemverFromAnnotations(annotations, curChannel)).Should(Equal(expected)) + }) +}) + +var _ = Describe("FindMaxSemver", func() { + It("Should return the maximal semantic version from the given channel and semver list", func() { + curChannel := "stable-v1.2.0" + semverList := []string{"v1.0.0", "v1.1.0", "v1.3.0", "v1.4.0"} + semVerChannelMappings := map[string]string{ + "v1.0.0": "stable-v1.0.0", + "v1.1.0": "stable-v1.1.0", + "v1.3.0": "stable-v1.3.0", + "v1.4.0": "stable-v1.4.0", + } + expected := "stable-v1.4.0" + Expect(FindMaxSemver(curChannel, semverList, semVerChannelMappings)).Should(Equal(expected)) + }) + + It("Should return the current channel if it exists in the semver list", func() { + curChannel := "stable-v1.2.0" + semverList := []string{"v1.0.0", "v1.1.0", "v1.2.0", "v1.3.0"} + semVerChannelMappings := map[string]string{ + "v1.0.0": "stable-v1.0.0", + "v1.1.0": "stable-v1.1.0", + "v1.2.0": "stable-v1.2.0", + "v1.3.0": "stable-v1.3.0", + } + expected := "stable-v1.2.0" + Expect(FindMaxSemver(curChannel, semverList, semVerChannelMappings)).Should(Equal(expected)) + }) + + It("Should return an empty string if the semver list is empty", func() { + curChannel := "stable-v1.2.0" + semverList := []string{} + semVerChannelMappings := map[string]string{} + expected := "" + Expect(FindMaxSemver(curChannel, semverList, semVerChannelMappings)).Should(Equal(expected)) + }) + + It("Should return an empty string if the semVerChannelMappings has no valid mappings", func() { + curChannel := "stable-v1.2.0" + semverList := []string{"v1.4.0", "v1.5.0", "v1.6.0"} + semVerChannelMappings := map[string]string{ + "v1.0.0": "stable-v1.0.0", + "v1.1.0": "stable-v1.1.0", + "v1.3.0": "stable-v1.3.0", + } + expected := "" + Expect(FindMaxSemver(curChannel, semverList, semVerChannelMappings)).Should(Equal(expected)) + }) +}) + +var _ = Describe("RemoveObjectField", func() { + It("Should remove the specified field from the object", func() { + obj := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "test", + "namespace": "default", + }, + "spec": map[string]interface{}{ + "replicas": 3, + }, + }, + } + + RemoveObjectField(obj.Object, ".metadata.namespace") + + expected := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "test", + }, + "spec": map[string]interface{}{ + "replicas": 3, + }, + }, + } + + Expect(obj).Should(Equal(expected)) + }) + + It("Should remove nested fields from the object", func() { + obj := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "test", + "namespace": "default", + "labels": map[string]interface{}{ + "app": "myapp", + }, + }, + "spec": map[string]interface{}{ + "replicas": 3, + }, + }, + } + + RemoveObjectField(obj.Object, ".metadata.labels.app") + + expected := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "test", + "namespace": "default", + "labels": map[string]interface{}{}, + }, + "spec": map[string]interface{}{ + "replicas": 3, + }, + }, + } + + Expect(obj).Should(Equal(expected)) + }) + + It("Should do nothing if the field does not exist", func() { + obj := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "test", + "namespace": "default", + }, + "spec": map[string]interface{}{ + "replicas": 3, + }, + }, + } + + RemoveObjectField(obj.Object, ".metadata.labels.app") + + expected := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "test", + "namespace": "default", + }, + "spec": map[string]interface{}{ + "replicas": 3, + }, + }, + } + + Expect(obj).Should(Equal(expected)) + }) + + It("Should remove field and all its nested fields from the object", func() { + obj := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "test", + "namespace": "default", + "labels": map[string]interface{}{ + "app": "myapp", + }, + }, + "spec": map[string]interface{}{ + "replicas": 3, + }, + }, + } + + RemoveObjectField(obj.Object, ".metadata") + + expected := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "spec": map[string]interface{}{ + "replicas": 3, + }, + }, + } + + Expect(obj).Should(Equal(expected)) + }) +}) + +var _ = Describe("AddObjectField", func() { + It("Should add a new field to the object", func() { + obj := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "test", + }, + "spec": map[string]interface{}{ + "replicas": 3, + }, + }, + } + + AddObjectField(obj.Object, ".metadata.namespace", "default") + + expected := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "test", + "namespace": "default", + }, + "spec": map[string]interface{}{ + "replicas": 3, + }, + }, + } + + Expect(obj).Should(Equal(expected)) + }) + + It("Should add a nested field to the object", func() { + obj := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "test", + }, + "spec": map[string]interface{}{ + "replicas": 3, + }, + }, + } + + AddObjectField(obj.Object, ".metadata.labels.app", "myapp") + + expected := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "test", + "labels": map[string]interface{}{ + "app": "myapp", + }, + }, + "spec": map[string]interface{}{ + "replicas": 3, + }, + }, + } + + Expect(obj).Should(Equal(expected)) + }) + + It("Should overwrite an existing field in the object", func() { + obj := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "test", + }, + "spec": map[string]interface{}{ + "replicas": 3, + }, + }, + } + + AddObjectField(obj.Object, ".spec.replicas", 5) + + expected := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "test", + }, + "spec": map[string]interface{}{ + "replicas": 5, + }, + }, + } + + Expect(obj).Should(Equal(expected)) + }) + + It("Should create nested maps if they do not exist", func() { + obj := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "test", + }, + }, + } + + AddObjectField(obj.Object, ".spec.template.spec.containers[0].name", "nginx") + + expected := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "test", + }, + "spec": map[string]interface{}{ + "template": map[string]interface{}{ + "spec": map[string]interface{}{ + "containers": []interface{}{ + map[string]interface{}{ + "name": "nginx", + }, + }, + }, + }, + }, + }, + } + + Expect(obj).Should(Equal(expected)) + }) +}) diff --git a/docs/design/operand-deployment-lifecycle-manager.md b/docs/design/operand-deployment-lifecycle-manager.md index bef60831..dac88306 100644 --- a/docs/design/operand-deployment-lifecycle-manager.md +++ b/docs/design/operand-deployment-lifecycle-manager.md @@ -73,7 +73,6 @@ spec: sourceNamespace: openshift-marketplace [9] installMode: cluster [10] installPlanApproval: Manual [11] - supportStatus: continuous [12] ``` The OperandRegistry Custom Resource (CR) lists OLM Operator information for operands that may be requested for installation and/or access by an application that runs in a namespace. The registry CR specifies: @@ -87,9 +86,8 @@ The OperandRegistry Custom Resource (CR) lists OLM Operator information for oper 7. (optional) `scope` is an indicator, either public or private, that dictates whether deployment can be requested from other namespaces (public) or only from the namespace of this OperandRegistry (private). The default value is private. 8. `sourceName` is the name of the CatalogSource. 9. `sourceNamespace` is the namespace of the CatalogSource. -10. (optional) `installMode` is the install mode of the operator, can be either `namespace` (OLM one namespace) or `cluster` (OLM all namespaces). The default value is `namespace`. Operator is deployed in `openshift-operators` namespace when InstallMode is set to `cluster`. +10. (optional) `installMode` is the install mode of the operator, can be `namespace` (OLM one namespace), `cluster` (OLM all namespaces) or `no-op` (discontinued service). The default value is `namespace`. Operator is deployed in `openshift-operators` namespace when InstallMode is set to `cluster`. 11. (optional) `installPlanApproval` is the approval mode for emitted installplan. The default value is `Automatic`. -12. (optional) `supportStatus` is the support status for services. (1) When SupportStatus is `continous`, operator is supported to be fresh deployed via OperandRequest from scratch. (2) When SupportStatus is `maintained`, operator is not supported to be fresh deployed via OperandRequest, only upgrade and deletion are allowed. The default value is `continous` ## OperandConfig Spec diff --git a/go.mod b/go.mod index 19cf2190..e0b7b60b 100644 --- a/go.mod +++ b/go.mod @@ -1,86 +1,113 @@ -module github.com/IBM/operand-deployment-lifecycle-manager +module github.com/IBM/operand-deployment-lifecycle-manager/v4 -go 1.17 +go 1.23 require ( - github.com/IBM/controller-filtered-cache v0.3.2 - github.com/IBM/ibm-namespace-scope-operator v1.0.0-alpha - github.com/coreos/etcd-operator v0.9.4 + github.com/IBM/controller-filtered-cache v0.3.5 + github.com/IBM/ibm-namespace-scope-operator v1.17.3 + github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df github.com/deckarep/golang-set v1.7.1 + github.com/google/go-cmp v0.5.9 + github.com/jaegertracing/jaeger-operator v1.36.0 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 - github.com/onsi/ginkgo v1.16.4 - github.com/onsi/gomega v1.14.0 + github.com/onsi/ginkgo v1.16.5 + github.com/onsi/ginkgo/v2 v2.1.4 + github.com/onsi/gomega v1.19.0 + github.com/openshift/api v0.0.0-20220124143425-d74727069f6f github.com/operator-framework/api v0.6.2 github.com/operator-framework/operator-lifecycle-manager v0.17.0 github.com/pkg/errors v0.9.1 - golang.org/x/mod v0.7.0 - k8s.io/api v0.21.3 - k8s.io/apimachinery v0.21.3 - k8s.io/client-go v0.21.3 + github.com/stretchr/testify v1.9.0 + golang.org/x/mod v0.17.0 + k8s.io/api v0.24.3 + k8s.io/apimachinery v0.24.17 + k8s.io/client-go v0.24.3 k8s.io/klog v1.0.0 - sigs.k8s.io/controller-runtime v0.9.6 + k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 + sigs.k8s.io/controller-runtime v0.12.3 sigs.k8s.io/kubebuilder v1.0.9-0.20200805184228-f7a3b65dd250 ) require ( - cloud.google.com/go v0.54.0 // indirect + cloud.google.com/go/compute v1.23.0 // indirect + cloud.google.com/go/compute/metadata v0.2.3 // indirect + github.com/PuerkitoBio/purell v1.1.1 // indirect + github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver v3.5.1+incompatible // indirect github.com/blang/semver/v4 v4.0.0 // indirect - github.com/buger/jsonparser v1.1.1 // indirect - github.com/cespare/xxhash/v2 v2.1.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/evanphx/json-patch v4.11.0+incompatible // indirect - github.com/fsnotify/fsnotify v1.4.9 // indirect - github.com/go-logr/logr v0.4.0 // indirect - github.com/go-logr/zapr v0.4.0 // indirect + github.com/emicklei/go-restful/v3 v3.10.0 // indirect + github.com/evanphx/json-patch v4.12.0+incompatible // indirect + github.com/fsnotify/fsnotify v1.5.4 // indirect + github.com/go-logr/logr v1.2.3 // indirect + github.com/go-logr/zapr v1.2.0 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.19.5 // indirect + github.com/go-openapi/swag v0.19.14 // indirect github.com/gobuffalo/flect v0.2.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect - github.com/golang/protobuf v1.5.2 // indirect - github.com/google/go-cmp v0.5.5 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/gnostic v0.6.9 // indirect github.com/google/gofuzz v1.1.0 // indirect - github.com/google/uuid v1.1.2 // indirect - github.com/googleapis/gnostic v0.5.5 // indirect - github.com/hashicorp/golang-lru v0.5.4 // indirect + github.com/google/uuid v1.3.1 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect github.com/imdario/mergo v0.3.12 // indirect - github.com/json-iterator/go v1.1.11 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/magiconair/properties v1.8.6 // indirect + github.com/mailru/easyjson v0.7.6 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.1 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/nxadm/tail v1.4.8 // indirect + github.com/openshift/elasticsearch-operator v0.0.0-20220708171007-a87102296ded // indirect github.com/operator-framework/operator-registry v1.13.6 // indirect - github.com/prometheus/client_golang v1.11.0 // indirect + github.com/pelletier/go-toml v1.9.5 // indirect + github.com/pelletier/go-toml/v2 v2.0.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_golang v1.12.2 // indirect github.com/prometheus/client_model v0.2.0 // indirect - github.com/prometheus/common v0.26.0 // indirect - github.com/prometheus/procfs v0.6.0 // indirect - github.com/sirupsen/logrus v1.7.0 // indirect + github.com/prometheus/common v0.32.1 // indirect + github.com/prometheus/procfs v0.7.3 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/spf13/afero v1.8.2 // indirect + github.com/spf13/cast v1.5.0 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/viper v1.12.0 // indirect + github.com/stretchr/objx v0.5.2 // indirect + github.com/subosito/gotenv v1.3.0 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect - go.uber.org/zap v1.18.1 // indirect - golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 // indirect - golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect - golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect - golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d // indirect - golang.org/x/text v0.3.6 // indirect - golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect + go.uber.org/zap v1.19.1 // indirect + golang.org/x/net v0.33.0 // indirect + golang.org/x/oauth2 v0.11.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/term v0.27.0 // indirect + golang.org/x/text v0.21.0 // indirect + golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a // indirect - google.golang.org/grpc v1.30.0 // indirect - google.golang.org/protobuf v1.26.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect + google.golang.org/grpc v1.59.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/ini.v1 v1.66.4 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect - k8s.io/apiextensions-apiserver v0.21.3 // indirect - k8s.io/component-base v0.21.3 // indirect - k8s.io/klog/v2 v2.8.0 // indirect - k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7 // indirect - k8s.io/utils v0.0.0-20210722164352-7f3ee0f31471 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect - sigs.k8s.io/yaml v1.2.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/apiextensions-apiserver v0.24.2 // indirect + k8s.io/component-base v0.24.2 // indirect + k8s.io/klog/v2 v2.60.1 // indirect + k8s.io/kube-openapi v0.0.0-20220627174259-011e075b9cb8 // indirect + sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect ) // fix vulnerability: CVE-2021-3121 in github.com/gogo/protobuf v1.2.1 diff --git a/go.sum b/go.sum index 39228614..62405276 100644 --- a/go.sum +++ b/go.sum @@ -5,37 +5,60 @@ cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7h cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0 h1:3ithwDMr7/3vpAMXiH+ZQnYbuIsh+OPhUPMFC9enmn0= cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY= +cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= -github.com/Azure/go-autorest/autorest v0.11.12/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw= +github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= +github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= @@ -44,14 +67,15 @@ github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935 github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= 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/IBM/controller-filtered-cache v0.3.2 h1:ghyg9s/NyrlyLvZayj65jy4Vcc1dSyD2cee5fp6Ki8Q= -github.com/IBM/controller-filtered-cache v0.3.2/go.mod h1:gEDzSQxUwcdScwsw59MTwchTjh6vzLWaSPffIkr85U4= -github.com/IBM/ibm-namespace-scope-operator v1.0.0-alpha h1:xU3IXZ2ZDumuL36mjSqcPZa3mS7XUJSXQYLR7VMUGy8= -github.com/IBM/ibm-namespace-scope-operator v1.0.0-alpha/go.mod h1:wkE4MMVf3IKwsu90Gxy4No/S5gZ/PC6EHKcoXKiX21Q= +github.com/IBM/controller-filtered-cache v0.3.5 h1:XoT+7B12jGeWHZBe3qOnXNCJukGtXP+MUVnu0Lq4BvM= +github.com/IBM/controller-filtered-cache v0.3.5/go.mod h1:Lu4gJQZ9TqO9FiQXq+JA5gXs8/4AoFmo4ee+PD5Lp/A= +github.com/IBM/ibm-namespace-scope-operator v1.17.3 h1:AS5EpRrHFxxGBR57KrEZu7MzkCQQNCoCBH4quu3XB3Q= +github.com/IBM/ibm-namespace-scope-operator v1.17.3/go.mod h1:tQ3ENUpO4IprjZypcH8UCzd0fT0fDYNlUuffGgyMZv8= github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver/v3 v3.0.3/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= @@ -69,8 +93,10 @@ github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 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= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= @@ -85,16 +111,22 @@ github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk5 github.com/alessio/shellescape v0.0.0-20190409004728-b115ca0f9053/go.mod h1:xW8sBma2LE3QxFSzCnH9qe6gAE2yO9GvQaWwX89HxbE= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210826220005-b48c857c3a0e/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= github.com/aws/aws-sdk-go v1.17.7/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df h1:GSoSVRLoBaFpOOds6QyY1L8AX7uoY+Ln3BHc22W40X0= +github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df/go.mod h1:hiVxq5OP2bUGBRNS3Z/bt/reCLFNbdcST6gISi1fiOM= +github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -115,7 +147,6 @@ github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2y github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bshuster-repo/logrus-logstash-hook v0.4.1 h1:pgAtgj+A31JBVtEHu2uHuEx0n+2ukqUJnS2vVe5pQNA= github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= -github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= github.com/bugsnag/bugsnag-go v1.5.3 h1:yeRUT3mUE13jL1tGwvoQsKdVbAsQx9AJ+fqahKveP04= @@ -125,20 +156,29 @@ github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3k github.com/bugsnag/panicwrap v1.2.0 h1:OzrKrRvXis8qEvOkfcxNcYbOd2O7xXS2nnKMEMABFQA= github.com/bugsnag/panicwrap v1.2.0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= +github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM= -github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= +github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= +github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f h1:tSNMc+rJDfmYntojat8lljbt1mgKNpTxUZJsSzJ9Y1s= github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= @@ -159,19 +199,19 @@ github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kw github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/etcd-operator v0.9.4 h1:NkN7HxlYHl4vOY8+etHRA+zDedwYYLhKfIsxdEPM+Lc= -github.com/coreos/etcd-operator v0.9.4/go.mod h1:h6zWPsRcUpzmi9C3kEE5HZqy1oo+jK4VtjdemOxySbE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -186,6 +226,11 @@ github.com/cznic/ql v1.2.0/go.mod h1:FbpzhyZrqr0PVlK6ury+PoW3T0ODUV22OeWIxcaOrSE github.com/cznic/sortutil v0.0.0-20150617083342-4c7342852e65/go.mod h1:q2w6Bg5jeox1B+QkJ6Wp/+Vn0G/bo3f1uY7Fn3vivIQ= github.com/cznic/strutil v0.0.0-20171016134553-529a34b1c186/go.mod h1:AHHPPPXTw0h6pVabbcbyGRK1DckRn7r/STdZEeIDzZc= github.com/cznic/zappy v0.0.0-20160723133515-2533cb5b45cc/go.mod h1:Y1SNZ4dRUOKXshKUbwUapqNncRrho4mkjQebgEHZLj8= +github.com/dave/dst v0.26.2/go.mod h1:UMDJuIRPfyUCC78eFuB+SV/WI8oDeyFDvM/JR6NI3IU= +github.com/dave/gopackages v0.0.0-20170318123100-46e7023ec56e/go.mod h1:i00+b/gKdIDIxuLDFob7ustLAVqhsZRk2qVZrArELGQ= +github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= +github.com/dave/kerr v0.0.0-20170318121727-bc25dd6abe8e/go.mod h1:qZqlPyPvfsDJt+3wHJ1EvSXDuVjFTK0j2p/ca+gtsb8= +github.com/dave/rebecca v0.9.1/go.mod h1:N6XYdMD/OKw3lkF3ywh8Z6wPGuwNFDNtWYEMFWEmXBA= 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= @@ -238,29 +283,43 @@ github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkg 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= +github.com/emicklei/go-restful/v3 v3.10.0 h1:X4gma4HM7hFm6WMeAsTfqA0GOfdNoCzBIkHGoRLGXuM= +github.com/emicklei/go-restful/v3 v3.10.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v4.11.0+incompatible h1:glyUF9yIYtMHzn8xaKw5rMhdWcwsYV8dZHIq5567/xs= -github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= +github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239/go.mod h1:Gdwt2ce0yfBxPvZrHkprdPPTTS3N5rwmLE8T22KBXlw= github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fsouza/fake-gcs-server v1.7.0/go.mod h1:5XIRs4YvwNbNoz+1JF8j6KLAyDh7RHGAyAK3EP2EsNk= github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= github.com/garyburd/redigo v1.6.0 h1:0VruCpn7yAIIu7pWVClQC8wxCJEcG3nyzpMSHKi1PQc= github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= +github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= +github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -280,12 +339,13 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v0.3.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc= -github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= github.com/go-logr/zapr v0.2.0/go.mod h1:qhKdvif7YF5GI9NWEpyxTSSBdGmzkNguibrdCNVPunU= -github.com/go-logr/zapr v0.4.0 h1:uc1uML3hRYL9/ZZPdgHS/n8Nzo+eaYL/Efxkkamf7OM= -github.com/go-logr/zapr v0.4.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= +github.com/go-logr/zapr v1.2.0 h1:n4JnPI1T3Qq1SFEi/F8rwLrZERp2bso19PJZDB9dayk= +github.com/go-logr/zapr v1.2.0/go.mod h1:Qa4Bsj2Vb+FAVeAKsLD8RLQ+YRJB8YDmOAKxaBQf7Ro= github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= @@ -299,11 +359,15 @@ github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwds github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM= +github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= @@ -318,7 +382,6 @@ github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsd github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= -github.com/go-openapi/spec v0.19.5/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= @@ -328,6 +391,8 @@ github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/ github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= +github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= @@ -336,7 +401,6 @@ github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+ github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/gobuffalo/flect v0.1.5/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80= github.com/gobuffalo/flect v0.2.0/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80= github.com/gobuffalo/flect v0.2.1 h1:GPoRjEN0QObosV4XwuoWvSd5uSiL0N3e91/xqyY4crQ= github.com/gobuffalo/flect v0.2.1/go.mod h1:vmkQwuZYhN5Pc4ljYQZzP+1sq+NEkK+lh20jmEmX3jc= @@ -344,6 +408,7 @@ github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJA github.com/goccy/go-yaml v1.8.1/go.mod h1:wS4gNoLalDSJxo/SpngzPQ2BN4uuZVLCmbM4S3vd4+Y= github.com/gocql/gocql v0.0.0-20190301043612-f6df8288f9b4/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0= github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/flock v0.7.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/uuid v3.3.0+incompatible h1:8K4tyRfvU1CYPgJsveYFQMhpFd/wXNM7iK6rR7UHz84= github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= @@ -352,23 +417,29 @@ github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69 github.com/golang-migrate/migrate/v4 v4.6.2 h1:LDDOHo/q1W5UDj6PbkxdCv7lv9yunyZHXvxuwDkGo3k= github.com/golang-migrate/migrate/v4 v4.6.2/go.mod h1:JYi6reN3+Z734VZ0akNuyOJNcrg45ZL7LDBMW3WGJL0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= @@ -378,8 +449,10 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -388,15 +461,25 @@ github.com/golangplus/fmt v0.0.0-20150411045040-2a5d6d7d2995/go.mod h1:lJgMEyOkY github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/cel-go v0.10.1/go.mod h1:U7ayypeSkw23szu4GaQTPJGx66c20mx8JklMSxrmI1w= +github.com/google/cel-spec v0.6.0/go.mod h1:Nwjgxy5CbjlPrtCWjeDjUyKMl8w41YBYGjsyDdqk0xA= +github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= +github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0= +github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= @@ -404,16 +487,27 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181127221834-b4f47329b966/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= @@ -421,8 +515,8 @@ github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTV github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= -github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw= github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= @@ -431,8 +525,9 @@ github.com/gorilla/handlers v1.4.2 h1:0QniY0USkHQ1RGCLfKxeNHK9bkDHGRYGNDFBCS+YAR github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.2 h1:zoNxOV7WjqXptQOVngLmcSQgXmgk4NMz1HibBchjl/I= github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= @@ -440,9 +535,11 @@ github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16 github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-health-probe v0.3.2/go.mod h1:izVOQ4RWbjUR6lm4nn+VLJyQ+FyaiGmprEYgI04Gs7U= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= @@ -462,8 +559,8 @@ github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= @@ -473,11 +570,11 @@ github.com/hokaccha/go-prettyjson v0.0.0-20190818114111-108c894c2c0e/go.mod h1:p github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= @@ -489,6 +586,8 @@ github.com/itchyny/go-flags v1.5.0/go.mod h1:lenkYuCobuxLBAd/HGFE4LRoW8D3B6iXRQf github.com/itchyny/gojq v0.11.0/go.mod h1:my6D2qN2Sm6qa+/5GsPDUZlCWGR+U8Qsa9he76sudv0= github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= github.com/jackc/pgx v3.2.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= +github.com/jaegertracing/jaeger-operator v1.36.0 h1:dtzmxvtO4ipR85W0/x8sxLNL2NnAip5OLmVvpK3yFNE= +github.com/jaegertracing/jaeger-operator v1.36.0/go.mod h1:PU9SDt8Krj/hZ4KqlsGNHfsbjt3bnrF+Cm4ztQeGdjs= github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869/go.mod h1:cJ6Cj7dQo+O6GJNiMx+Pa94qKj+TG8ONdKHgMNIyyag= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= @@ -496,14 +595,18 @@ github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= 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= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= @@ -517,9 +620,12 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -535,12 +641,16 @@ github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9 github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= +github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= @@ -577,24 +687,29 @@ github.com/mitchellh/hashstructure v1.0.0/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1D github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= -github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= +github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= 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= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= @@ -616,9 +731,12 @@ github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+ github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= -github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.1.4 h1:GNapqRSid3zijZ9H77KrgVG4/8KqiyRsxcSxe+7ApXY= +github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= @@ -628,8 +746,8 @@ github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoT github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.14.0 h1:ep6kpPVwmr/nTbklSx2nrLNSIO62DoYAhnPNIMhK8gI= -github.com/onsi/gomega v1.14.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= +github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= @@ -645,8 +763,14 @@ github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.m github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= github.com/openshift/api v0.0.0-20200326152221-912866ddb162/go.mod h1:RKMJ5CBnljLfnej+BJ/xnOWc3kZDvJUaIAEq2oKSPtE= github.com/openshift/api v0.0.0-20200331152225-585af27e34fd/go.mod h1:RKMJ5CBnljLfnej+BJ/xnOWc3kZDvJUaIAEq2oKSPtE= +github.com/openshift/api v0.0.0-20220124143425-d74727069f6f h1:iOTv1WudhVm2UsoST+L+ZrA5A9w57h9vmQsdlBuqG6g= +github.com/openshift/api v0.0.0-20220124143425-d74727069f6f/go.mod h1:F/eU6jgr6Q2VhMu1mSpMmygxAELd7+BUxs3NHZ25jV4= github.com/openshift/build-machinery-go v0.0.0-20200211121458-5e3d6e570160/go.mod h1:1CkcsT3aVebzRBzVTSbiKSkJMsC/CASqxesfqEMfJEc= +github.com/openshift/build-machinery-go v0.0.0-20211213093930-7e33a7eb4ce3/go.mod h1:b1BuldmJlbA/xYtdZvKi+7j5YGB44qJUJDZ9zwiNCfE= github.com/openshift/client-go v0.0.0-20200326155132-2a6cd50aedd0/go.mod h1:uUQ4LClRO+fg5MF/P6QxjMCb1C9f7Oh4RKepftDnEJE= +github.com/openshift/elasticsearch-operator v0.0.0-20220708171007-a87102296ded h1:TJJaeg/XIFV0Vr25vjAZISU/kMr5QUKk3vBLXLhmRQE= +github.com/openshift/elasticsearch-operator v0.0.0-20220708171007-a87102296ded/go.mod h1:6dxhWPY3Wr/0b0eGrFpV7gcyeS+ne48Mo9OQ9dxrLNI= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/operator-framework/api v0.3.7-0.20200602203552-431198de9fc2/go.mod h1:Xbje9x0SHmh0nihE21kpesB38vk3cyxnE6JdDS8Jo1Q= github.com/operator-framework/api v0.3.20/go.mod h1:Xbje9x0SHmh0nihE21kpesB38vk3cyxnE6JdDS8Jo1Q= @@ -666,6 +790,10 @@ github.com/pbnjay/strptime v0.0.0-20140226051138-5c05b0d668c9/go.mod h1:6Hr+C/ol 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/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU= +github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc= github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= @@ -676,6 +804,8 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.9.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= 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= @@ -689,8 +819,10 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_golang v1.12.2 h1:51L9cDoUHVrXx4zWYlcLQIZ+d+VXHgqnYKkIuq4g/34= +github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= @@ -706,8 +838,9 @@ github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8 github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= @@ -716,18 +849,22 @@ github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7z github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= 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/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= @@ -740,25 +877,38 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= +github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= +github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v0.0.7/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= -github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= +github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= +github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= +github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= +github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -768,10 +918,14 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= +github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= 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/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 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= @@ -779,15 +933,20 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.5.0/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/subosito/gotenv v1.3.0 h1:mjC+YW8QpAdXibNi+vNWgzmgBH4+5l5dCXv8cNysBLI= +github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/tebeka/strftime v0.1.3/go.mod h1:7wJm3dZlpr4l/oVK0t1HYIc4rMzQ2XJlOMIUJUJH6XQ= github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= @@ -800,11 +959,17 @@ github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2 github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= github.com/xeipuuv/gojsonschema v1.1.0/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= github.com/yvasiyarov/go-metrics v0.0.0-20150112132944-c25f46c4b940 h1:p7OofyZ509h8DmPLh8Hn+EIIZm/xYhdZHJ9GnXHdr6U= github.com/yvasiyarov/go-metrics v0.0.0-20150112132944-c25f46c4b940/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= @@ -818,10 +983,21 @@ gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2 go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= -go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= +go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= +go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= +go.etcd.io/etcd/client/v3 v3.5.1/go.mod h1:OnjH4M8OnAotwaB2l9bVgZzRFKru7/ZMoS46OtKyd3Q= +go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE= +go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc= +go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4= go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.0/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= @@ -830,15 +1006,33 @@ go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= +go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= +go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM= +go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= +go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= +go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= +go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE= +go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= +go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= +go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= @@ -847,8 +1041,11 @@ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9E go.uber.org/zap v1.8.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= -go.uber.org/zap v1.18.1 h1:CSUJ2mjFszzEWt4CdKISEuChVIXGBn3lAPwkRGyVrc4= -go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI= +go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= +golang.org/x/arch v0.0.0-20180920145803-b19384d3c130/go.mod h1:cYlCBUl1MsqxdiKgmc4uh7TxZfWSFLOGSRR090WDxt8= golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -869,7 +1066,10 @@ golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -893,8 +1093,9 @@ golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= @@ -903,10 +1104,12 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449 h1:xUIPaMhvROX9dhPvRCenIJtU78+lbEenGbgqB5hfHCQ= -golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= -golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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= @@ -945,33 +1148,66 @@ golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= -golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU= +golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180903190138-2b024373dcd9/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1021,26 +1257,53 @@ golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE= -golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1048,16 +1311,19 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= -golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= +golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1105,20 +1371,39 @@ golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200403190813-44a64ad78b9b/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200616133436-c1934b75d054/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200616195046-dc31b401abb5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.6-0.20210820212750-d4cc65f0b2ff/go.mod h1:YD9qOF0M9xpSpdWTBbzEl5e/RnCefISl8E5Noe10jFM= +golang.org/x/tools v0.1.10-0.20220218145154-897bd77cd717/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= 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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= gomodules.xyz/jsonpatch/v2 v2.1.0/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= @@ -1139,7 +1424,18 @@ google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsb google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1170,12 +1466,41 @@ google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvx google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200701001935-0939c5918c31/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a h1:pOwg4OoaRYScjmR4LlLgdtnyoHYTSAVhhqe5uPdpII8= +google.golang.org/genproto v0.0.0-20201102152239-715cce707fb0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= @@ -1189,8 +1514,22 @@ google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQ google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.30.0 h1:M5a8xTlYTxwMn5ZFkwhRabsygDY5G8TYLyQDBxJNAxE= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= +google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= google.golang.org/grpc/cmd/protoc-gen-go-grpc v0.0.0-20200709232328-d8193ee9cc3e/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -1203,8 +1542,10 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= 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= @@ -1223,15 +1564,19 @@ gopkg.in/imdario/mergo.v0 v0.3.7/go.mod h1:9qPP6AGrlC1G2PTNXko614FwGZvorN7MiBU0E gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4= +gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473/go.mod h1:N1eN2tsCx0Ydtgjl4cqmbRCsY4/+z4cYDeqwZTk6zog= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/src-d/go-billy.v4 v4.3.0/go.mod h1:tm33zBoOwxjYHZIE+OV8bxTWFMJLrconzFMd38aARFk= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -1244,8 +1589,9 @@ gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= @@ -1257,70 +1603,69 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.17.2/go.mod h1:BS9fjjLc4CMuqfSO8vgbHPKMt5+SF0ET6u/RVDihTo4= k8s.io/api v0.18.0/go.mod h1:q2HRQkfDzHMBZL9l/y9rH63PkQl4vae0xRT+8prbrK8= k8s.io/api v0.18.2/go.mod h1:SJCWI7OLzhZSvbY7U8zwNl9UA4o1fizoug34OV/2r78= -k8s.io/api v0.18.6/go.mod h1:eeyxr+cwCjMdLAmr2W3RyDI0VvTawSg/3RFFBEnmZGI= k8s.io/api v0.18.9/go.mod h1:9u/h6sUh6FxfErv7QqetX1EB3yBMIYOBXzdcf0Gf0rc= k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= -k8s.io/api v0.20.5/go.mod h1:FQjAceXnVaWDeov2YUWhOb6Yt+5UjErkp6UO3nczO1Y= -k8s.io/api v0.21.3 h1:cblWILbLO8ar+Fj6xdDGr603HRsf8Wu9E9rngJeprZQ= -k8s.io/api v0.21.3/go.mod h1:hUgeYHUbBp23Ue4qdX9tR8/ANi/g3ehylAqDn9NWVOg= +k8s.io/api v0.23.0/go.mod h1:8wmDdLBHBNxtOIytwLstXt5E9PddnZb0GaMcqsvDBpg= +k8s.io/api v0.24.2/go.mod h1:AHqbSkTm6YrQ0ObxjO3Pmp/ubFF/KuM7jU+3khoBsOg= +k8s.io/api v0.24.3 h1:tt55QEmKd6L2k5DP6G/ZzdMQKvG5ro4H4teClqm0sTY= +k8s.io/api v0.24.3/go.mod h1:elGR/XSZrS7z7cSZPzVWaycpJuGIw57j9b95/1PdJNI= k8s.io/apiextensions-apiserver v0.17.2/go.mod h1:4KdMpjkEjjDI2pPfBA15OscyNldHWdBCfsWMDWAmSTs= k8s.io/apiextensions-apiserver v0.18.2/go.mod h1:q3faSnRGmYimiocj6cHQ1I3WpLqmDgJFlKL37fC4ZvY= -k8s.io/apiextensions-apiserver v0.18.6/go.mod h1:lv89S7fUysXjLZO7ke783xOwVTm6lKizADfvUM/SS/M= k8s.io/apiextensions-apiserver v0.18.9/go.mod h1:JagmAhU0TVENzgUZqHJsjCSDh7YuV5o6g01G1Fwh7zI= k8s.io/apiextensions-apiserver v0.20.1/go.mod h1:ntnrZV+6a3dB504qwC5PN/Yg9PBiDNt1EVqbW2kORVk= -k8s.io/apiextensions-apiserver v0.21.3 h1:+B6biyUWpqt41kz5x6peIsljlsuwvNAp/oFax/j2/aY= -k8s.io/apiextensions-apiserver v0.21.3/go.mod h1:kl6dap3Gd45+21Jnh6utCx8Z2xxLm8LGDkprcd+KbsE= +k8s.io/apiextensions-apiserver v0.24.2 h1:/4NEQHKlEz1MlaK/wHT5KMKC9UKYz6NZz6JE6ov4G6k= +k8s.io/apiextensions-apiserver v0.24.2/go.mod h1:e5t2GMFVngUEHUd0wuCJzw8YDwZoqZfJiGOW6mm2hLQ= k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= k8s.io/apimachinery v0.17.2/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= k8s.io/apimachinery v0.18.0/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA= k8s.io/apimachinery v0.18.2/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA= -k8s.io/apimachinery v0.18.6/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= k8s.io/apimachinery v0.18.9/go.mod h1:PF5taHbXgTEJLU+xMypMmYTXTWPJ5LaW8bfsisxnEXk= k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= -k8s.io/apimachinery v0.20.5/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= -k8s.io/apimachinery v0.21.3 h1:3Ju4nvjCngxxMYby0BimUk+pQHPOQp3eCGChk5kfVII= -k8s.io/apimachinery v0.21.3/go.mod h1:H/IM+5vH9kZRNJ4l3x/fXP/5bOPJaVP/guptnZPeCFI= +k8s.io/apimachinery v0.23.0/go.mod h1:fFCTTBKvKcwTPFzjlcxp91uPFZr+JA0FubU4fLzzFYc= +k8s.io/apimachinery v0.24.2/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= +k8s.io/apimachinery v0.24.3/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= +k8s.io/apimachinery v0.24.17 h1:mewWCeZ3Swr4EAfatVAhHXJHGzCHojphWA/5UJW4pPY= +k8s.io/apimachinery v0.24.17/go.mod h1:kSzhCwldu9XB172NDdLffRN0sJ3x95RR7Bmyc4SHhs0= k8s.io/apiserver v0.17.2/go.mod h1:lBmw/TtQdtxvrTk0e2cgtOxHizXI+d0mmGQURIHQZlo= k8s.io/apiserver v0.18.2/go.mod h1:Xbh066NqrZO8cbsoenCwyDJ1OSi8Ag8I2lezeHxzwzw= -k8s.io/apiserver v0.18.6/go.mod h1:Zt2XvTHuaZjBz6EFYzpp+X4hTmgWGy8AthNVnTdm3Wg= k8s.io/apiserver v0.18.9/go.mod h1:vXQzMtUCLsGg1Bh+7Jo2mZKHpHZFCZn8eTNSepcIA1M= k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= -k8s.io/apiserver v0.21.3/go.mod h1:eDPWlZG6/cCCMj/JBcEpDoK+I+6i3r9GsChYBHSbAzU= +k8s.io/apiserver v0.24.2/go.mod h1:pSuKzr3zV+L+MWqsEo0kHHYwCo77AT5qXbFXP2jbvFI= k8s.io/cli-runtime v0.17.2/go.mod h1:aa8t9ziyQdbkuizkNLAw3qe3srSyWh9zlSB7zTqRNPI= k8s.io/cli-runtime v0.18.0/go.mod h1:1eXfmBsIJosjn9LjEBUd2WVPoPAY9XGTqTFcPMIBsUQ= k8s.io/client-go v0.17.2/go.mod h1:QAzRgsa0C2xl4/eVpeVAZMvikCn8Nm81yqVx3Kk9XYI= k8s.io/client-go v0.18.0/go.mod h1:uQSYDYs4WhVZ9i6AIoEZuwUggLVEF64HOD37boKAtF8= k8s.io/client-go v0.18.2/go.mod h1:Xcm5wVGXX9HAA2JJ2sSBUn3tCJ+4SVlCbl2MNNv+CIU= -k8s.io/client-go v0.18.6/go.mod h1:/fwtGLjYMS1MaM5oi+eXhKwG+1UHidUEXRh6cNsdO0Q= k8s.io/client-go v0.18.9/go.mod h1:UjkEetDmr40P9NX0Ok3Idt08FCf2I4mIHgjFsot77uY= k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= -k8s.io/client-go v0.20.5/go.mod h1:Ee5OOMMYvlH8FCZhDsacjMlCBwetbGZETwo1OA+e6Zw= -k8s.io/client-go v0.21.3 h1:J9nxZTOmvkInRDCzcSNQmPJbDYN/PjlxXT9Mos3HcLg= -k8s.io/client-go v0.21.3/go.mod h1:+VPhCgTsaFmGILxR/7E1N0S+ryO010QBeNCv5JwRGYU= +k8s.io/client-go v0.24.2/go.mod h1:zg4Xaoo+umDsfCWr4fCnmLEtQXyCNXCvJuSsglNcV30= +k8s.io/client-go v0.24.3 h1:Nl1840+6p4JqkFWEW2LnMKU667BUxw03REfLAVhuKQY= +k8s.io/client-go v0.24.3/go.mod h1:AAovolf5Z9bY1wIg2FZ8LPQlEdKHjLI7ZD4rw920BJw= k8s.io/code-generator v0.17.2/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s= k8s.io/code-generator v0.18.0/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc= k8s.io/code-generator v0.18.2/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc= -k8s.io/code-generator v0.18.6/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c= k8s.io/code-generator v0.18.9/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c= k8s.io/code-generator v0.20.1/go.mod h1:UsqdF+VX4PU2g46NC2JRs4gc+IfrctnwHb76RNbWHJg= -k8s.io/code-generator v0.21.3/go.mod h1:K3y0Bv9Cz2cOW2vXUrNZlFbflhuPvuadW6JdnN6gGKo= +k8s.io/code-generator v0.23.0/go.mod h1:vQvOhDXhuzqiVfM/YHp+dmg10WDZCchJVObc9MvowsE= +k8s.io/code-generator v0.24.2/go.mod h1:dpVhs00hTuTdTY6jvVxvTFCk6gSMrtfRydbhZwHI15w= k8s.io/component-base v0.17.2/go.mod h1:zMPW3g5aH7cHJpKYQ/ZsGMcgbsA/VyhEugF3QT1awLs= k8s.io/component-base v0.18.0/go.mod h1:u3BCg0z1uskkzrnAKFzulmYaEpZF7XC9Pf/uFyb1v2c= k8s.io/component-base v0.18.2/go.mod h1:kqLlMuhJNHQ9lz8Z7V5bxUUtjFZnrypArGl58gmDfUM= -k8s.io/component-base v0.18.6/go.mod h1:knSVsibPR5K6EW2XOjEHik6sdU5nCvKMrzMt2D4In14= k8s.io/component-base v0.18.9/go.mod h1:tUo4qZtV8m7t/U+0DgY+fcnn4BFZ480fZdzxOkWH4zk= k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= -k8s.io/component-base v0.21.3 h1:4WuuXY3Npa+iFfi2aDRiOz+anhNvRfye0859ZgfC5Og= -k8s.io/component-base v0.21.3/go.mod h1:kkuhtfEHeZM6LkX0saqSK8PbdO7A0HigUngmhhrwfGQ= +k8s.io/component-base v0.24.2 h1:kwpQdoSfbcH+8MPN4tALtajLDfSfYxBDYlXobNWI6OU= +k8s.io/component-base v0.24.2/go.mod h1:ucHwW76dajvQ9B7+zecZAP3BVqvrHoOxm8olHEg0nmM= 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/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20201113003025-83324d819ded/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/gengo v0.0.0-20211129171323-c02415ce4185/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= 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= k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= @@ -1328,15 +1673,18 @@ k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.8.0 h1:Q3gmuM9hKEjefWFFYF0Mat+YyFJvsUyYuwyNNJ5C9Ts= -k8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= +k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/klog/v2 v2.60.1 h1:VW25q3bZx9uE3vvdL6M8ezOX79vA2Aq1nEWLqNQclHc= +k8s.io/klog/v2 v2.60.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-aggregator v0.18.9/go.mod h1:ik5Mf6JaP2M9XbWZR/AYgXx2Nj4EDBrHyakUx7C8cdw= k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= -k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7 h1:vEx13qjvaZ4yfObSSXW7BrMc/KQBBT/Jyee8XtLf4x0= -k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE= +k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= +k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42/go.mod h1:Z/45zLw8lUo4wdiUkI+v/ImEGAvu3WatcZl3lPMR4Rk= +k8s.io/kube-openapi v0.0.0-20220627174259-011e075b9cb8 h1:yEQKdMCjzAOvGeiTwG4hO/hNVNtDOuUFvMUZ0OlaIzs= +k8s.io/kube-openapi v0.0.0-20220627174259-011e075b9cb8/go.mod h1:mbJ+NSUoAhuR14N0S63bPkh8MGVSo3VYSGZtH/mfMe0= k8s.io/kubectl v0.17.2/go.mod h1:y4rfLV0n6aPmvbRCqZQjvOp3ezxsFgpqL+zF5jH/lxk= k8s.io/kubectl v0.18.0/go.mod h1:LOkWx9Z5DXMEg5KtOjHhRiC1fqJPLyCr3KtQgEolCkU= k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= @@ -1344,10 +1692,11 @@ k8s.io/metrics v0.17.2/go.mod h1:3TkNHET4ROd+NfzNxkjoVfQ0Ob4iZnaHmSEA4vYpwLw= k8s.io/metrics v0.18.0/go.mod h1:8aYTW18koXqjLVKL7Ds05RPMX9ipJZI3mywYvBOxXd4= k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= -k8s.io/utils v0.0.0-20200603063816-c1c6865ac451/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20210722164352-7f3ee0f31471 h1:DnzUXII7sVg1FJ/4JX6YDRJfLNAC7idRatPwe07suiI= -k8s.io/utils v0.0.0-20210722164352-7f3ee0f31471/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 h1:HNSDgDCrr/6Ly3WEGKZftiE7IY19Vz2GdbOCyI4qqhc= +k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/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= @@ -1360,27 +1709,31 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.19/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.30/go.mod h1:fEO7lRTdivWO2qYVCVG7dEADOMo/MLDCVr8So2g88Uw= sigs.k8s.io/controller-runtime v0.6.0/go.mod h1:CpYf5pdNY/B352A1TFLAS2JVSlnGQ5O2cftPHndTroo= -sigs.k8s.io/controller-runtime v0.6.2/go.mod h1:vhcq/rlnENJ09SIRp3EveTaZ0yqH526hjf9iJdbUJ/E= sigs.k8s.io/controller-runtime v0.8.0/go.mod h1:v9Lbj5oX443uR7GXYY46E0EE2o7k2YxQ58GxVNeXSW4= -sigs.k8s.io/controller-runtime v0.9.6 h1:EevVMlgUj4fC1NVM4+DB3iPkWkmGRNarA66neqv9Qew= -sigs.k8s.io/controller-runtime v0.9.6/go.mod h1:q6PpkM5vqQubEKUKOM6qr06oXGzOBcCby1DA9FbyZeA= +sigs.k8s.io/controller-runtime v0.12.3 h1:FCM8xeY/FI8hoAfh/V4XbbYMY20gElh9yh+A98usMio= +sigs.k8s.io/controller-runtime v0.12.3/go.mod h1:qKsk4WE6zW2Hfj0G4v10EnNB2jMG1C+NTb8h+DwCoU0= sigs.k8s.io/controller-tools v0.3.0/go.mod h1:enhtKGfxZD1GFEoMgP8Fdbu+uKQ/cq1/WGJhdVChfvI= sigs.k8s.io/controller-tools v0.4.1/go.mod h1:G9rHdZMVlBDocIxGkK3jHLWqcTMNvveypYJwrvYKjWU= +sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs= +sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 h1:kDi4JBNAsJWfz1aEXhO8Jg87JJaPNLh5tIzYHgStQ9Y= +sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2/go.mod h1:B+TnT182UBxE84DiCz4CVE26eOSDAeYCpfDnC2kdKMY= sigs.k8s.io/kind v0.7.0/go.mod h1:An/AbWHT6pA/Lm0Og8j3ukGhfJP3RiVN/IBU6Lo3zl8= sigs.k8s.io/kubebuilder v1.0.9-0.20200805184228-f7a3b65dd250 h1:1D9gplyencP89oP5a8Whr5mUYwcv308kaMNNBBw+dR8= sigs.k8s.io/kubebuilder v1.0.9-0.20200805184228-f7a3b65dd250/go.mod h1:lkExAOdnNf9BGrvi4lWHCMo1fa6xtENt/QVwDhWpK+c= sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= -sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06 h1:zD2IemQ4LmOcAumeiyDWXKUI2SO0NYDe3H6QGvPOVgU= sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18= 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/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3nu+sPzynno= sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= +sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= -sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= vbom.ml/util v0.0.0-20160121211510-db5cfe13f5cc/go.mod h1:so/NYdZXCz+E3ZpW0uAoCj6uzU2+8OWDFv/HxUSs7kI= diff --git a/helm-cluster-scoped/Chart.yaml b/helm-cluster-scoped/Chart.yaml new file mode 100644 index 00000000..fe69038a --- /dev/null +++ b/helm-cluster-scoped/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: ibm-odlm-cluster-scoped +description: A Helm chart for ibm-odlm +type: application +version: 4.4.0 +appVersion: 4.4.0 \ No newline at end of file diff --git a/helm-cluster-scoped/templates/cluster-rbac.yaml b/helm-cluster-scoped/templates/cluster-rbac.yaml new file mode 100644 index 00000000..a0d98524 --- /dev/null +++ b/helm-cluster-scoped/templates/cluster-rbac.yaml @@ -0,0 +1,2 @@ +### no cluster rbac for ODLM +# if it is v3 -> v4 upgrade we need to add permission to cleanup certmanager, auditlogging and licensing CR \ No newline at end of file diff --git a/helm-cluster-scoped/templates/cluster-webhook.yaml b/helm-cluster-scoped/templates/cluster-webhook.yaml new file mode 100644 index 00000000..6bb71452 --- /dev/null +++ b/helm-cluster-scoped/templates/cluster-webhook.yaml @@ -0,0 +1 @@ +### no webhook for ODLM \ No newline at end of file diff --git a/helm-cluster-scoped/templates/crd.yaml b/helm-cluster-scoped/templates/crd.yaml new file mode 100644 index 00000000..9e137995 --- /dev/null +++ b/helm-cluster-scoped/templates/crd.yaml @@ -0,0 +1,3973 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + creationTimestamp: null + # labels: + # app.kubernetes.io/instance: operand-deployment-lifecycle-manager + # app.kubernetes.io/managed-by: operand-deployment-lifecycle-manager + # app.kubernetes.io/name: operand-deployment-lifecycle-manager + name: operandbindinfos.operator.ibm.com +spec: + group: operator.ibm.com + names: + kind: OperandBindInfo + listKind: OperandBindInfoList + plural: operandbindinfos + shortNames: + - opbi + singular: operandbindinfo + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - description: Current Phase + jsonPath: .status.phase + name: Phase + type: string + - jsonPath: .metadata.creationTimestamp + name: Created At + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: OperandBindInfo is the Schema for the operandbindinfoes API. + Documentation For additional details regarding install parameters check + https://ibm.biz/icpfs39install. License By installing this product you accept + the license terms https://ibm.biz/icpfs39license + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: OperandBindInfoSpec defines the desired state of OperandBindInfo. + properties: + bindings: + additionalProperties: + description: |- + Bindable is a Kubernetes resources to be shared from one namespace to another. + List of supported resources are Secrets, Configmaps, Services, and Routes. + Secrets and Configmaps will be copied such that a new Secret/Configmap with + exactly the same data will be created in the target namespace. + Services and Routes data will be copied into a configmap in the target + namespace. + properties: + configmap: + description: The configmap identifies an existing configmap + object. if it exists, the ODLM will share to the namespace + of the OperandRequest. + type: string + route: + description: |- + Route data will be shared by copying it into a configmap which is then + created in the target namespace + properties: + data: + additionalProperties: + type: string + description: |- + Data is a key-value pair where the value is a YAML path to a value in the + OpenShift Route, e.g. .spec.host or .spec.tls.termination + type: object + name: + description: Name is the name of the OpenShift Route resource + type: string + type: object + secret: + description: The secret identifies an existing secret. if it + exists, the ODLM will share to the namespace of the OperandRequest. + type: string + service: + description: |- + Service data will be shared by copying it into a configmap which is then + created in the target namespace + properties: + data: + additionalProperties: + type: string + description: |- + Data is a key-value pair where the value is a YAML path to a value in the + Kubernetes Service, e.g. .spec.ports[0]port + type: object + name: + description: Name is the name of the Kubernetes Service + resource + type: string + type: object + type: object + description: The bindings section is used to specify information about + the access/configuration data that is to be shared. + type: object + description: + type: string + operand: + description: |- + The deployed service identifies itself with its operand. + This must match the name in the OperandRegistry in the current namespace. + type: string + registry: + description: The registry identifies the name of the name of the OperandRegistry + CR from which this operand deployment is being requested. + type: string + registryNamespace: + description: |- + Specifies the namespace in which the OperandRegistry reside. + The default is the current namespace in which the request is defined. + type: string + required: + - operand + - registry + type: object + x-kubernetes-preserve-unknown-fields: true + status: + description: OperandBindInfoStatus defines the observed state of OperandBindInfo. + properties: + phase: + description: Phase describes the overall phase of OperandBindInfo. + type: string + requestNamespaces: + description: RequestNamespaces defines the namespaces of OperandRequest. + items: + type: string + type: array + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null + +--- + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + creationTimestamp: null + # labels: + # app.kubernetes.io/instance: operand-deployment-lifecycle-manager + # app.kubernetes.io/managed-by: operand-deployment-lifecycle-manager + # app.kubernetes.io/name: operand-deployment-lifecycle-manager + name: operandconfigs.operator.ibm.com +spec: + group: operator.ibm.com + names: + kind: OperandConfig + listKind: OperandConfigList + plural: operandconfigs + shortNames: + - opcon + singular: operandconfig + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - description: Current Phase + jsonPath: .status.phase + name: Phase + type: string + - jsonPath: .metadata.creationTimestamp + name: Created At + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: OperandConfig is the Schema for the operandconfigs API. Documentation + For additional details regarding install parameters check https://ibm.biz/icpfs39install. + License By installing this product you accept the license terms https://ibm.biz/icpfs39license + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: OperandConfigSpec defines the desired state of OperandConfig. + properties: + services: + description: Services is a list of configuration of service. + items: + description: ConfigService defines the configuration of the service. + properties: + name: + description: Name is the subscription name. + type: string + resources: + description: Resources is used to specify the kubernetes resources + that are needed for the service. + items: + description: ConfigResource defines the resource needed for + the service + properties: + annotations: + additionalProperties: + type: string + description: Annotations are the annotations used in the + resource. + type: object + apiVersion: + description: APIVersion defines the versioned schema of + this representation of an object. + type: string + data: + description: Data is the configuration map of kubernetes + resource. + nullable: true + type: object + x-kubernetes-preserve-unknown-fields: true + force: + default: true + description: Force is used to determine whether the existing + kubernetes resource should be overwritten. + type: boolean + kind: + description: Kind identifies the kind of the kubernetes + resource. + type: string + labels: + additionalProperties: + type: string + description: Labels are the labels used in the resource. + type: object + name: + description: Name is the resource name. + type: string + namespace: + description: Namespace is the namespace of the resource. + type: string + optionalFields: + description: OptionalFields is the list of fields that + could be updated additionally. + items: + description: OptionalField defines the optional field + for the resource. + properties: + matchExpressions: + description: MatchExpressions is the match expression + of the field. + items: + description: MatchExpression defines the match + expression of the field. + properties: + key: + description: Key is the key of the field. + type: string + objectRef: + description: ObjectRef is the reference of + the object. + properties: + apiVersion: + description: APIVersion is the version + of the object. + type: string + kind: + description: Kind is the kind of the object. + type: string + name: + description: Name is the name of the object. + type: string + namespace: + description: Namespace is the namespace + of the object. + type: string + required: + - apiVersion + - kind + - name + type: object + operator: + description: Operator is the operator of the + field. + type: string + values: + description: Values is the values of the field. + items: + type: string + type: array + required: + - key + - operator + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + operation: + description: Operation is the operation of the field. + type: string + path: + description: Path is the json path of the field. + type: string + valueFrom: + description: ValueFrom is the field value from the + object + properties: + objectRef: + description: ObjectRef is the reference of the + object. + properties: + apiVersion: + description: APIVersion is the version of + the object. + type: string + kind: + description: Kind is the kind of the object. + type: string + name: + description: Name is the name of the object. + type: string + namespace: + description: Namespace is the namespace + of the object. + type: string + required: + - apiVersion + - kind + - name + type: object + path: + description: Path is the json path of the field. + type: string + required: + - path + type: object + required: + - operation + - path + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + ownerReferences: + description: OwnerReferences is the list of owner references. + items: + properties: + apiVersion: + description: API version of the referent. + type: string + blockOwnerDeletion: + description: |- + If true, AND if the owner has the "foregroundDeletion" finalizer, then + the owner cannot be deleted from the key-value store until this + reference is removed. + Defaults to false. + type: boolean + controller: + description: |- + If true, this reference points to the managing controller. + Default is false. + type: boolean + kind: + description: Kind of the referent. + type: string + name: + description: Name of the referent. + type: string + required: + - apiVersion + - kind + - name + type: object + type: array + required: + - apiVersion + - kind + - name + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + spec: + additionalProperties: + type: object + x-kubernetes-preserve-unknown-fields: true + description: Spec is the configuration map of custom resource. + type: object + state: + description: State is a flag to enable or disable service. + type: string + required: + - name + type: object + type: array + type: object + x-kubernetes-preserve-unknown-fields: true + status: + description: OperandConfigStatus defines the observed state of OperandConfig. + properties: + phase: + description: Phase describes the overall phase of operands in the + OperandConfig. + type: string + serviceStatus: + additionalProperties: + description: CrStatus defines the status of the custom resource. + properties: + customResourceStatus: + additionalProperties: + description: ServicePhase defines the service status. + type: string + type: object + type: object + description: ServiceStatus defines all the status of a operator. + type: object + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null + +--- + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + creationTimestamp: null + # labels: + # app.kubernetes.io/instance: operand-deployment-lifecycle-manager + # app.kubernetes.io/managed-by: operand-deployment-lifecycle-manager + # app.kubernetes.io/name: operand-deployment-lifecycle-manager + name: operandregistries.operator.ibm.com +spec: + group: operator.ibm.com + names: + kind: OperandRegistry + listKind: OperandRegistryList + plural: operandregistries + shortNames: + - opreg + singular: operandregistry + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - description: Current Phase + jsonPath: .status.phase + name: Phase + type: string + - jsonPath: .metadata.creationTimestamp + name: Created At + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: OperandRegistry is the Schema for the operandregistries API. + Documentation For additional details regarding install parameters check + https://ibm.biz/icpfs39install. License By installing this product you accept + the license terms https://ibm.biz/icpfs39license + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: OperandRegistrySpec defines the desired state of OperandRegistry. + properties: + operators: + description: Operators is a list of operator OLM definition. + items: + description: Operator defines the desired state of Operators. + properties: + channel: + description: Name of the channel to track. + type: string + description: + description: Description of a common service. + type: string + fallbackChannels: + description: List of channels to fallback when the main channel + is not available. + items: + type: string + type: array + installMode: + description: |- + The install mode of an operator, either namespace or cluster. + Valid values are: + - "namespace" (default): operator is deployed in namespace of OperandRegistry; + - "cluster": operator is deployed in "openshift-operators" namespace; + - "no-op": operator is not supported to be fresh deployed; + type: string + installPlanApproval: + description: |- + Approval mode for emitted InstallPlans. + Valid values are: + - "Automatic" (default): operator will be installed automatically; + - "Manual": operator installation will be pending until users approve it; + type: string + name: + description: A unique name for the operator whose operand may + be deployed. + type: string + namespace: + description: |- + The namespace in which operator should be deployed when InstallMode is empty or set to "namespace". + If the namespace is not set, the operator namespace is the same as OperandRegistry Namespace + type: string + operatorConfig: + description: OperatorConfig is the name of the OperatorConfig + type: string + packageName: + description: Name of the package that defines the applications. + type: string + scope: + description: |- + A scope indicator, either public or private. + Valid values are: + - "private" (default): deployment only request from the containing names; + - "public": deployment can be requested from other namespaces; + enum: + - public + - private + type: string + sourceName: + description: Name of a CatalogSource that defines where and + how to find the channel. + type: string + sourceNamespace: + description: The Kubernetes namespace where the CatalogSource + used is located. + type: string + startingCSV: + description: StartingCSV of the installation. + type: string + subscriptionConfig: + description: SubscriptionConfig is used to override operator + configuration. + properties: + env: + description: |- + Env is a list of environment variables to set in the container. + Cannot be updated. + items: + description: EnvVar represents an environment variable + present in a Container. + properties: + name: + description: Name of the environment variable. Must + be a C_IDENTIFIER. + type: string + value: + description: |- + Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in the container and + any service environment variables. If a variable cannot be resolved, + the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless of whether the variable + exists or not. + Defaults to "". + type: string + valueFrom: + description: Source for the environment variable's + value. Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + optional: + description: Specify whether the ConfigMap + or its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + description: |- + Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, + spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in + the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. + properties: + containerName: + description: 'Container name: required for + volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of + the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + description: Selects a key of a secret in the + pod's namespace + properties: + key: + description: The key of the secret to select + from. Must be a valid secret key. + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + optional: + description: Specify whether the Secret or + its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + envFrom: + description: |- + EnvFrom is a list of sources to populate environment variables in the container. + The keys defined within a source must be a C_IDENTIFIER. All invalid keys + will be reported as an event when the container is starting. When a key exists in multiple + sources, the value associated with the last source will take precedence. + Values defined by an Env with a duplicate key will take precedence. + Immutable. + items: + description: EnvFromSource represents the source of a + set of ConfigMaps + properties: + configMapRef: + description: The ConfigMap to select from + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + optional: + description: Specify whether the ConfigMap must + be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + prefix: + description: An optional identifier to prepend to + each key in the ConfigMap. Must be a C_IDENTIFIER. + type: string + secretRef: + description: The Secret to select from + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + optional: + description: Specify whether the Secret must be + defined + type: boolean + type: object + x-kubernetes-map-type: atomic + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is a selector which must be true for the pod to fit on a node. + Selector which must match a node's labels for the pod to be scheduled on that node. + More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ + type: object + resources: + description: |- + Resources represents compute resources required by this container. + Immutable. + More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/ + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + selector: + description: |- + Selector is the label selector for pods to be configured. + Existing ReplicaSets whose pods are + selected by this will be the ones affected by this deployment. + It must match the pod template's labels. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + tolerations: + description: Tolerations are the pod's tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + volumeMounts: + description: List of VolumeMounts to set in the container. + items: + description: VolumeMount describes a mounting of a Volume + within a container. + properties: + mountPath: + description: |- + Path within the container at which the volume should be mounted. Must + not contain ':'. + type: string + mountPropagation: + description: |- + mountPropagation determines how mounts are propagated from the host + to container and the other way around. + When not set, MountPropagationNone is used. + This field is beta in 1.10. + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: |- + Mounted read-only if true, read-write otherwise (false or unspecified). + Defaults to false. + type: boolean + subPath: + description: |- + Path within the volume from which the container's volume should be mounted. + Defaults to "" (volume's root). + type: string + subPathExpr: + description: |- + Expanded path within the volume from which the container's volume should be mounted. + Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. + Defaults to "" (volume's root). + SubPathExpr and SubPath are mutually exclusive. + type: string + required: + - mountPath + - name + type: object + type: array + volumes: + description: List of Volumes to set in the podSpec. + items: + description: Volume represents a named volume in a pod + that may be accessed by any container in the pod. + properties: + awsElasticBlockStore: + description: |- + awsElasticBlockStore represents an AWS Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + properties: + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + TODO: how do we prevent errors in the filesystem from compromising the machine + type: string + partition: + description: |- + partition is the partition in the volume that you want to mount. + If omitted, the default is to mount by volume name. + Examples: For volume /dev/sda1, you specify the partition as "1". + Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty). + format: int32 + type: integer + readOnly: + description: |- + readOnly value true will force the readOnly setting in VolumeMounts. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + type: boolean + volumeID: + description: |- + volumeID is unique ID of the persistent disk resource in AWS (Amazon EBS volume). + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + type: string + required: + - volumeID + type: object + azureDisk: + description: azureDisk represents an Azure Data Disk + mount on the host and bind mount to the pod. + properties: + cachingMode: + description: 'cachingMode is the Host Caching + mode: None, Read Only, Read Write.' + type: string + diskName: + description: diskName is the Name of the data + disk in the blob storage + type: string + diskURI: + description: diskURI is the URI of data disk in + the blob storage + type: string + fsType: + description: |- + fsType is Filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + kind: + description: 'kind expected values are Shared: + multiple blob disks per storage account Dedicated: + single blob disk per storage account Managed: + azure managed data disk (only in managed availability + set). defaults to shared' + type: string + readOnly: + description: |- + readOnly Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + description: azureFile represents an Azure File Service + mount on the host and bind mount to the pod. + properties: + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretName: + description: secretName is the name of secret + that contains Azure Storage Account Name and + Key + type: string + shareName: + description: shareName is the azure share Name + type: string + required: + - secretName + - shareName + type: object + cephfs: + description: cephFS represents a Ceph FS mount on + the host that shares a pod's lifetime + properties: + monitors: + description: |- + monitors is Required: Monitors is a collection of Ceph monitors + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + items: + type: string + type: array + path: + description: 'path is Optional: Used as the mounted + root, rather than the full Ceph tree, default + is /' + type: string + readOnly: + description: |- + readOnly is Optional: Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: boolean + secretFile: + description: |- + secretFile is Optional: SecretFile is the path to key ring for User, default is /etc/ceph/user.secret + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: string + secretRef: + description: |- + secretRef is Optional: SecretRef is reference to the authentication secret for User, default is empty. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + user: + description: |- + user is optional: User is the rados user name, default is admin + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: string + required: + - monitors + type: object + cinder: + description: |- + cinder represents a cinder volume attached and mounted on kubelets host machine. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: boolean + secretRef: + description: |- + secretRef is optional: points to a secret object containing parameters used to connect + to OpenStack. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + volumeID: + description: |- + volumeID used to identify the volume in cinder. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: string + required: + - volumeID + type: object + configMap: + description: configMap represents a configMap that + should populate this volume + properties: + defaultMode: + description: |- + defaultMode is optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path within + a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + optional: + description: optional specify whether the ConfigMap + or its keys must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + csi: + description: csi (Container Storage Interface) represents + ephemeral storage that is handled by certain external + CSI drivers (Beta feature). + properties: + driver: + description: |- + driver is the name of the CSI driver that handles this volume. + Consult with your admin for the correct name as registered in the cluster. + type: string + fsType: + description: |- + fsType to mount. Ex. "ext4", "xfs", "ntfs". + If not provided, the empty value is passed to the associated CSI driver + which will determine the default filesystem to apply. + type: string + nodePublishSecretRef: + description: |- + nodePublishSecretRef is a reference to the secret object containing + sensitive information to pass to the CSI driver to complete the CSI + NodePublishVolume and NodeUnpublishVolume calls. + This field is optional, and may be empty if no secret is required. If the + secret object contains more than one secret, all secret references are passed. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + readOnly: + description: |- + readOnly specifies a read-only configuration for the volume. + Defaults to false (read/write). + type: boolean + volumeAttributes: + additionalProperties: + type: string + description: |- + volumeAttributes stores driver-specific properties that are passed to the CSI + driver. Consult your driver's documentation for supported values. + type: object + required: + - driver + type: object + downwardAPI: + description: downwardAPI represents downward API about + the pod that should populate this volume + properties: + defaultMode: + description: |- + Optional: mode bits to use on created files by default. Must be a + Optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: Items is a list of downward API volume + file + items: + description: DownwardAPIVolumeFile represents + information to create the file containing + the pod field + properties: + fieldRef: + description: 'Required: Selects a field + of the pod: only annotations, labels, + name and namespace are supported.' + properties: + apiVersion: + description: Version of the schema the + FieldPath is written in terms of, + defaults to "v1". + type: string + fieldPath: + description: Path of the field to select + in the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + description: |- + Optional: mode bits used to set permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: 'Required: Path is the relative + path name of the file to be created. Must + not be absolute or contain the ''..'' + path. Must be utf-8 encoded. The first + item of the relative path must not start + with ''..''' + type: string + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. + properties: + containerName: + description: 'Container name: required + for volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format + of the exposed resources, defaults + to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to + select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + type: object + emptyDir: + description: |- + emptyDir represents a temporary directory that shares a pod's lifetime. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + properties: + medium: + description: |- + medium represents what type of storage medium should back this directory. + The default is "" which means to use the node's default medium. + Must be an empty string (default) or Memory. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + description: |- + sizeLimit is the total amount of local storage required for this EmptyDir volume. + The size limit is also applicable for memory medium. + The maximum usage on memory medium EmptyDir would be the minimum value between + the SizeLimit specified here and the sum of memory limits of all containers in a pod. + The default is nil which means that the limit is undefined. + More info: http://kubernetes.io/docs/user-guide/volumes#emptydir + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + ephemeral: + description: |- + ephemeral represents a volume that is handled by a cluster storage driver. + The volume's lifecycle is tied to the pod that defines it - it will be created before the pod starts, + and deleted when the pod is removed. + + + Use this if: + a) the volume is only needed while the pod runs, + b) features of normal volumes like restoring from snapshot or capacity + tracking are needed, + c) the storage driver is specified through a storage class, and + d) the storage driver supports dynamic volume provisioning through + a PersistentVolumeClaim (see EphemeralVolumeSource for more + information on the connection between this volume type + and PersistentVolumeClaim). + + + Use PersistentVolumeClaim or one of the vendor-specific + APIs for volumes that persist for longer than the lifecycle + of an individual pod. + + + Use CSI for light-weight local ephemeral volumes if the CSI driver is meant to + be used that way - see the documentation of the driver for + more information. + + + A pod can use both types of ephemeral volumes and + persistent volumes at the same time. + properties: + volumeClaimTemplate: + description: |- + Will be used to create a stand-alone PVC to provision the volume. + The pod in which this EphemeralVolumeSource is embedded will be the + owner of the PVC, i.e. the PVC will be deleted together with the + pod. The name of the PVC will be `-` where + `` is the name from the `PodSpec.Volumes` array + entry. Pod validation will reject the pod if the concatenated name + is not valid for a PVC (for example, too long). + + + An existing PVC with that name that is not owned by the pod + will *not* be used for the pod to avoid using an unrelated + volume by mistake. Starting the pod is then blocked until + the unrelated PVC is removed. If such a pre-created PVC is + meant to be used by the pod, the PVC has to updated with an + owner reference to the pod once the pod exists. Normally + this should not be necessary, but it may be useful when + manually reconstructing a broken cluster. + + + This field is read-only and no changes will be made by Kubernetes + to the PVC after it has been created. + + + Required, must not be nil. + properties: + metadata: + description: |- + May contain labels and annotations that will be copied into the PVC + when creating it. No other fields are allowed and will be rejected during + validation. + type: object + spec: + description: |- + The specification for the PersistentVolumeClaim. The entire content is + copied unchanged into the PVC that gets created from this + template. The same fields as in a PersistentVolumeClaim + are also valid here. + properties: + accessModes: + description: |- + accessModes contains the desired access modes the volume should have. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1 + items: + type: string + type: array + dataSource: + description: |- + dataSource field can be used to specify either: + * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot) + * An existing PVC (PersistentVolumeClaim) + If the provisioner or an external controller can support the specified data source, + it will create a new volume based on the contents of the specified data source. + If the AnyVolumeDataSource feature gate is enabled, this field will always have + the same contents as the DataSourceRef field. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource + being referenced + type: string + name: + description: Name is the name of resource + being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + dataSourceRef: + description: |- + dataSourceRef specifies the object from which to populate the volume with data, if a non-empty + volume is desired. This may be any local object from a non-empty API group (non + core object) or a PersistentVolumeClaim object. + When this field is specified, volume binding will only succeed if the type of + the specified object matches some installed volume populator or dynamic + provisioner. + This field will replace the functionality of the DataSource field and as such + if both fields are non-empty, they must have the same value. For backwards + compatibility, both fields (DataSource and DataSourceRef) will be set to the same + value automatically if one of them is empty and the other is non-empty. + There are two important differences between DataSource and DataSourceRef: + * While DataSource only allows two specific types of objects, DataSourceRef + allows any non-core object, as well as PersistentVolumeClaim objects. + * While DataSource ignores disallowed values (dropping them), DataSourceRef + preserves all values, and generates an error if a disallowed value is + specified. + (Beta) Using this field requires the AnyVolumeDataSource feature gate to be enabled. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource + being referenced + type: string + name: + description: Name is the name of resource + being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + resources: + description: |- + resources represents the minimum resources the volume should have. + If RecoverVolumeExpansionFailure feature is enabled users are allowed to specify resource requirements + that are lower than previous value but must still be higher than capacity recorded in the + status field of the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + selector: + description: selector is a label query + over volumes to consider for binding. + properties: + matchExpressions: + description: matchExpressions is a + list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + storageClassName: + description: |- + storageClassName is the name of the StorageClass required by the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1 + type: string + volumeMode: + description: |- + volumeMode defines what type of volume is required by the claim. + Value of Filesystem is implied when not included in claim spec. + type: string + volumeName: + description: volumeName is the binding + reference to the PersistentVolume backing + this claim. + type: string + type: object + required: + - spec + type: object + type: object + fc: + description: fc represents a Fibre Channel resource + that is attached to a kubelet's host machine and + then exposed to the pod. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + TODO: how do we prevent errors in the filesystem from compromising the machine + type: string + lun: + description: 'lun is Optional: FC target lun number' + format: int32 + type: integer + readOnly: + description: |- + readOnly is Optional: Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + targetWWNs: + description: 'targetWWNs is Optional: FC target + worldwide names (WWNs)' + items: + type: string + type: array + wwids: + description: |- + wwids Optional: FC volume world wide identifiers (wwids) + Either wwids or combination of targetWWNs and lun must be set, but not both simultaneously. + items: + type: string + type: array + type: object + flexVolume: + description: |- + flexVolume represents a generic volume resource that is + provisioned/attached using an exec based plugin. + properties: + driver: + description: driver is the name of the driver + to use for this volume. + type: string + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". The default filesystem depends on FlexVolume script. + type: string + options: + additionalProperties: + type: string + description: 'options is Optional: this field + holds extra command options if any.' + type: object + readOnly: + description: |- + readOnly is Optional: defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef is Optional: secretRef is reference to the secret object containing + sensitive information to pass to the plugin scripts. This may be + empty if no secret object is specified. If the secret object + contains more than one secret, all secrets are passed to the plugin + scripts. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + required: + - driver + type: object + flocker: + description: flocker represents a Flocker volume attached + to a kubelet's host machine. This depends on the + Flocker control service being running + properties: + datasetName: + description: |- + datasetName is Name of the dataset stored as metadata -> name on the dataset for Flocker + should be considered as deprecated + type: string + datasetUUID: + description: datasetUUID is the UUID of the dataset. + This is unique identifier of a Flocker dataset + type: string + type: object + gcePersistentDisk: + description: |- + gcePersistentDisk represents a GCE Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + properties: + fsType: + description: |- + fsType is filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + TODO: how do we prevent errors in the filesystem from compromising the machine + type: string + partition: + description: |- + partition is the partition in the volume that you want to mount. + If omitted, the default is to mount by volume name. + Examples: For volume /dev/sda1, you specify the partition as "1". + Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty). + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + format: int32 + type: integer + pdName: + description: |- + pdName is unique name of the PD resource in GCE. Used to identify the disk in GCE. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + type: string + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + type: boolean + required: + - pdName + type: object + gitRepo: + description: |- + gitRepo represents a git repository at a particular revision. + DEPRECATED: GitRepo is deprecated. To provision a container with a git repo, mount an + EmptyDir into an InitContainer that clones the repo using git, then mount the EmptyDir + into the Pod's container. + properties: + directory: + description: |- + directory is the target directory name. + Must not contain or start with '..'. If '.' is supplied, the volume directory will be the + git repository. Otherwise, if specified, the volume will contain the git repository in + the subdirectory with the given name. + type: string + repository: + description: repository is the URL + type: string + revision: + description: revision is the commit hash for the + specified revision. + type: string + required: + - repository + type: object + glusterfs: + description: |- + glusterfs represents a Glusterfs mount on the host that shares a pod's lifetime. + More info: https://examples.k8s.io/volumes/glusterfs/README.md + properties: + endpoints: + description: |- + endpoints is the endpoint name that details Glusterfs topology. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: string + path: + description: |- + path is the Glusterfs volume path. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: string + readOnly: + description: |- + readOnly here will force the Glusterfs volume to be mounted with read-only permissions. + Defaults to false. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: boolean + required: + - endpoints + - path + type: object + hostPath: + description: |- + hostPath represents a pre-existing file or directory on the host + machine that is directly exposed to the container. This is generally + used for system agents or other privileged things that are allowed + to see the host machine. Most containers will NOT need this. + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + --- + TODO(jonesdl) We need to restrict who can use host directory mounts and who can/can not + mount host directories as read/write. + properties: + path: + description: |- + path of the directory on the host. + If the path is a symlink, it will follow the link to the real path. + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + type: string + type: + description: |- + type for HostPath Volume + Defaults to "" + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + type: string + required: + - path + type: object + iscsi: + description: |- + iscsi represents an ISCSI Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + More info: https://examples.k8s.io/volumes/iscsi/README.md + properties: + chapAuthDiscovery: + description: chapAuthDiscovery defines whether + support iSCSI Discovery CHAP authentication + type: boolean + chapAuthSession: + description: chapAuthSession defines whether support + iSCSI Session CHAP authentication + type: boolean + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi + TODO: how do we prevent errors in the filesystem from compromising the machine + type: string + initiatorName: + description: |- + initiatorName is the custom iSCSI Initiator Name. + If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface + : will be created for the connection. + type: string + iqn: + description: iqn is the target iSCSI Qualified + Name. + type: string + iscsiInterface: + description: |- + iscsiInterface is the interface Name that uses an iSCSI transport. + Defaults to 'default' (tcp). + type: string + lun: + description: lun represents iSCSI Target Lun number. + format: int32 + type: integer + portals: + description: |- + portals is the iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port + is other than default (typically TCP ports 860 and 3260). + items: + type: string + type: array + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + type: boolean + secretRef: + description: secretRef is the CHAP Secret for + iSCSI target and initiator authentication + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + targetPortal: + description: |- + targetPortal is iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port + is other than default (typically TCP ports 860 and 3260). + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + description: |- + name of the volume. + Must be a DNS_LABEL and unique within the pod. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + nfs: + description: |- + nfs represents an NFS mount on the host that shares a pod's lifetime + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + properties: + path: + description: |- + path that is exported by the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: string + readOnly: + description: |- + readOnly here will force the NFS export to be mounted with read-only permissions. + Defaults to false. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: boolean + server: + description: |- + server is the hostname or IP address of the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + description: |- + persistentVolumeClaimVolumeSource represents a reference to a + PersistentVolumeClaim in the same namespace. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims + properties: + claimName: + description: |- + claimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims + type: string + readOnly: + description: |- + readOnly Will force the ReadOnly setting in VolumeMounts. + Default false. + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + description: photonPersistentDisk represents a PhotonController + persistent disk attached and mounted on kubelets + host machine + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + pdID: + description: pdID is the ID that identifies Photon + Controller persistent disk + type: string + required: + - pdID + type: object + portworxVolume: + description: portworxVolume represents a portworx + volume attached and mounted on kubelets host machine + properties: + fsType: + description: |- + fSType represents the filesystem type to mount + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs". Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + volumeID: + description: volumeID uniquely identifies a Portworx + volume + type: string + required: + - volumeID + type: object + projected: + description: projected items for all in one resources + secrets, configmaps, and downward API + properties: + defaultMode: + description: |- + defaultMode are the mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + sources: + description: sources is the list of volume projections + items: + description: Projection that may be projected + along with other supported volume types + properties: + configMap: + description: configMap information about + the configMap data to project + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to + a path within a volume. + properties: + key: + description: key is the key to + project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + optional: + description: optional specify whether + the ConfigMap or its keys must be + defined + type: boolean + type: object + x-kubernetes-map-type: atomic + downwardAPI: + description: downwardAPI information about + the downwardAPI data to project + properties: + items: + description: Items is a list of DownwardAPIVolume + file + items: + description: DownwardAPIVolumeFile + represents information to create + the file containing the pod field + properties: + fieldRef: + description: 'Required: Selects + a field of the pod: only annotations, + labels, name and namespace are + supported.' + properties: + apiVersion: + description: Version of the + schema the FieldPath is + written in terms of, defaults + to "v1". + type: string + fieldPath: + description: Path of the field + to select in the specified + API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + description: |- + Optional: mode bits used to set permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: 'Required: Path is the + relative path name of the file + to be created. Must not be absolute + or contain the ''..'' path. + Must be utf-8 encoded. The first + item of the relative path must + not start with ''..''' + type: string + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. + properties: + containerName: + description: 'Container name: + required for volumes, optional + for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the + output format of the exposed + resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource + to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + type: object + secret: + description: secret information about the + secret data to project + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to + a path within a volume. + properties: + key: + description: key is the key to + project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + optional: + description: optional field specify + whether the Secret or its key must + be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + serviceAccountToken: + description: serviceAccountToken is information + about the serviceAccountToken data to + project + properties: + audience: + description: |- + audience is the intended audience of the token. A recipient of a token + must identify itself with an identifier specified in the audience of the + token, and otherwise should reject the token. The audience defaults to the + identifier of the apiserver. + type: string + expirationSeconds: + description: |- + expirationSeconds is the requested duration of validity of the service + account token. As the token approaches expiration, the kubelet volume + plugin will proactively rotate the service account token. The kubelet will + start trying to rotate the token if the token is older than 80 percent of + its time to live or if the token is older than 24 hours.Defaults to 1 hour + and must be at least 10 minutes. + format: int64 + type: integer + path: + description: |- + path is the path relative to the mount point of the file to project the + token into. + type: string + required: + - path + type: object + type: object + type: array + type: object + quobyte: + description: quobyte represents a Quobyte mount on + the host that shares a pod's lifetime + properties: + group: + description: |- + group to map volume access to + Default is no group + type: string + readOnly: + description: |- + readOnly here will force the Quobyte volume to be mounted with read-only permissions. + Defaults to false. + type: boolean + registry: + description: |- + registry represents a single or multiple Quobyte Registry services + specified as a string as host:port pair (multiple entries are separated with commas) + which acts as the central registry for volumes + type: string + tenant: + description: |- + tenant owning the given Quobyte volume in the Backend + Used with dynamically provisioned Quobyte volumes, value is set by the plugin + type: string + user: + description: |- + user to map volume access to + Defaults to serivceaccount user + type: string + volume: + description: volume is a string that references + an already created Quobyte volume by name. + type: string + required: + - registry + - volume + type: object + rbd: + description: |- + rbd represents a Rados Block Device mount on the host that shares a pod's lifetime. + More info: https://examples.k8s.io/volumes/rbd/README.md + properties: + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd + TODO: how do we prevent errors in the filesystem from compromising the machine + type: string + image: + description: |- + image is the rados image name. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + keyring: + description: |- + keyring is the path to key ring for RBDUser. + Default is /etc/ceph/keyring. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + monitors: + description: |- + monitors is a collection of Ceph monitors. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + items: + type: string + type: array + pool: + description: |- + pool is the rados pool name. + Default is rbd. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: boolean + secretRef: + description: |- + secretRef is name of the authentication secret for RBDUser. If provided + overrides keyring. + Default is nil. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + user: + description: |- + user is the rados user name. + Default is admin. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + required: + - image + - monitors + type: object + scaleIO: + description: scaleIO represents a ScaleIO persistent + volume attached and mounted on Kubernetes nodes. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". + Default is "xfs". + type: string + gateway: + description: gateway is the host address of the + ScaleIO API Gateway. + type: string + protectionDomain: + description: protectionDomain is the name of the + ScaleIO Protection Domain for the configured + storage. + type: string + readOnly: + description: |- + readOnly Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef references to the secret for ScaleIO user and other + sensitive information. If this is not provided, Login operation will fail. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + sslEnabled: + description: sslEnabled Flag enable/disable SSL + communication with Gateway, default false + type: boolean + storageMode: + description: |- + storageMode indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned. + Default is ThinProvisioned. + type: string + storagePool: + description: storagePool is the ScaleIO Storage + Pool associated with the protection domain. + type: string + system: + description: system is the name of the storage + system as configured in ScaleIO. + type: string + volumeName: + description: |- + volumeName is the name of a volume already created in the ScaleIO system + that is associated with this volume source. + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + description: |- + secret represents a secret that should populate this volume. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + properties: + defaultMode: + description: |- + defaultMode is Optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values + for mode bits. Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: |- + items If unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path within + a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + optional: + description: optional field specify whether the + Secret or its keys must be defined + type: boolean + secretName: + description: |- + secretName is the name of the secret in the pod's namespace to use. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + type: string + type: object + storageos: + description: storageOS represents a StorageOS volume + attached and mounted on Kubernetes nodes. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef specifies the secret to use for obtaining the StorageOS API + credentials. If not specified, default values will be attempted. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + volumeName: + description: |- + volumeName is the human-readable name of the StorageOS volume. Volume + names are only unique within a namespace. + type: string + volumeNamespace: + description: |- + volumeNamespace specifies the scope of the volume within StorageOS. If no + namespace is specified then the Pod's namespace will be used. This allows the + Kubernetes name scoping to be mirrored within StorageOS for tighter integration. + Set VolumeName to any name to override the default behaviour. + Set to "default" if you are not using namespaces within StorageOS. + Namespaces that do not pre-exist within StorageOS will be created. + type: string + type: object + vsphereVolume: + description: vsphereVolume represents a vSphere volume + attached and mounted on kubelets host machine + properties: + fsType: + description: |- + fsType is filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + storagePolicyID: + description: storagePolicyID is the storage Policy + Based Management (SPBM) profile ID associated + with the StoragePolicyName. + type: string + storagePolicyName: + description: storagePolicyName is the storage + Policy Based Management (SPBM) profile name. + type: string + volumePath: + description: volumePath is the path that identifies + vSphere volume vmdk + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + type: object + targetNamespaces: + description: The target namespace of the OperatorGroups. + items: + type: string + type: array + userManaged: + description: UserManaged is a flag to indicate whether operator + is managed by user + type: boolean + required: + - channel + - name + - packageName + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + type: object + x-kubernetes-preserve-unknown-fields: true + status: + description: OperandRegistryStatus defines the observed state of OperandRegistry. + properties: + conditions: + description: Conditions represents the current state of the Request + Service. + items: + description: |- + Condition represents the current state of the Request Service. + A condition might not show up if it is not happening. + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + type: string + lastUpdateTime: + description: The last time this condition was updated. + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + required: + - status + - type + type: object + type: array + operatorsStatus: + additionalProperties: + description: OperatorStatus defines operators status and the number + of reconcile request. + properties: + phase: + description: Phase is the state of operator. + type: string + reconcileRequests: + description: ReconcileRequests stores the namespace/name of + all the requests. + items: + description: ReconcileRequest records the information of the + operandRequest. + properties: + name: + description: Name defines the name of request. + type: string + namespace: + description: Namespace defines the namespace of request. + type: string + required: + - name + - namespace + type: object + type: array + type: object + description: OperatorsStatus defines operators status and the number + of reconcile request. + type: object + phase: + description: Phase describes the overall phase of operators in the + OperandRegistry. + type: string + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null + +--- + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + creationTimestamp: null + # labels: + # app.kubernetes.io/instance: operand-deployment-lifecycle-manager + # app.kubernetes.io/managed-by: operand-deployment-lifecycle-manager + # app.kubernetes.io/name: operand-deployment-lifecycle-manager + name: operandrequests.operator.ibm.com +spec: + group: operator.ibm.com + names: + kind: OperandRequest + listKind: OperandRequestList + plural: operandrequests + shortNames: + - opreq + singular: operandrequest + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - description: Current Phase + jsonPath: .status.phase + name: Phase + type: string + - jsonPath: .metadata.creationTimestamp + name: Created At + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: OperandRequest is the Schema for the operandrequests API. Documentation + For additional details regarding install parameters check https://ibm.biz/icpfs39install. + License By installing this product you accept the license terms https://ibm.biz/icpfs39license + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: The OperandRequestSpec identifies one or more specific operands + (from a specific Registry) that should actually be installed. + properties: + requests: + description: Requests defines a list of operands installation. + items: + description: Request identifies a operand detail. + properties: + description: + description: Description is an optional description for the + request. + type: string + operands: + description: Operands defines a list of the OperandRegistry + entry for the operand to be deployed. + items: + description: Operand defines the name and binding information + for one operator. + properties: + apiVersion: + description: APIVersion defines the versioned schema of + this representation of an object. + type: string + bindings: + additionalProperties: + description: |- + Bindable is a Kubernetes resources to be shared from one namespace to another. + List of supported resources are Secrets, Configmaps, Services, and Routes. + Secrets and Configmaps will be copied such that a new Secret/Configmap with + exactly the same data will be created in the target namespace. + Services and Routes data will be copied into a configmap in the target + namespace. + properties: + configmap: + description: The configmap identifies an existing + configmap object. if it exists, the ODLM will + share to the namespace of the OperandRequest. + type: string + route: + description: |- + Route data will be shared by copying it into a configmap which is then + created in the target namespace + properties: + data: + additionalProperties: + type: string + description: |- + Data is a key-value pair where the value is a YAML path to a value in the + OpenShift Route, e.g. .spec.host or .spec.tls.termination + type: object + name: + description: Name is the name of the OpenShift + Route resource + type: string + type: object + secret: + description: The secret identifies an existing secret. + if it exists, the ODLM will share to the namespace + of the OperandRequest. + type: string + service: + description: |- + Service data will be shared by copying it into a configmap which is then + created in the target namespace + properties: + data: + additionalProperties: + type: string + description: |- + Data is a key-value pair where the value is a YAML path to a value in the + Kubernetes Service, e.g. .spec.ports[0]port + type: object + name: + description: Name is the name of the Kubernetes + Service resource + type: string + type: object + type: object + description: The bindings section is used to specify names + of secret and/or configmap. + type: object + instanceName: + description: |- + InstanceName is used when users want to deploy multiple custom resources. + It is the name of the custom resource. + type: string + kind: + description: |- + Kind is used when users want to deploy multiple custom resources. + Kind identifies the kind of the custom resource. + type: string + name: + description: Name of the operand to be deployed. + type: string + spec: + description: |- + Spec is used when users want to deploy multiple custom resources. + It is the configuration map of custom resource. + nullable: true + type: object + x-kubernetes-preserve-unknown-fields: true + required: + - name + type: object + type: array + registry: + description: Specifies the name in which the OperandRegistry + reside. + type: string + registryNamespace: + description: |- + Specifies the namespace in which the OperandRegistry reside. + The default is the current namespace in which the request is defined. + type: string + required: + - operands + - registry + type: object + type: array + required: + - requests + type: object + x-kubernetes-preserve-unknown-fields: true + status: + description: OperandRequestStatus defines the observed state of OperandRequest. + properties: + conditions: + description: Conditions represents the current state of the Request + Service. + items: + description: |- + Condition represents the current state of the Request Service. + A condition might not show up if it is not happening. + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + type: string + lastUpdateTime: + description: The last time this condition was updated. + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + required: + - status + - type + type: object + type: array + members: + description: Members represnets the current operand status of the + set. + items: + description: MemberStatus shows if the Operator is ready. + properties: + name: + description: The member name are the same as the subscription + name. + type: string + operandCRList: + description: OperandCRList shows the list of custom resource + created by OperandRequest. + items: + description: OperandCRMember defines a custom resource created + by OperandRequest. + properties: + apiVersion: + description: APIVersion is the APIVersion of the custom + resource. + type: string + kind: + description: Kind is the kind of the custom resource. + type: string + name: + description: Name is the name of the custom resource. + type: string + type: object + type: array + phase: + description: The operand phase include None, Creating, Running, + Failed. + properties: + operandPhase: + description: OperandPhase shows the deploy phase of the + operator instance. + type: string + operatorPhase: + description: OperatorPhase shows the deploy phase of the + operator. + type: string + type: object + required: + - name + type: object + type: array + phase: + description: Phase is the cluster running phase. + type: string + services: + description: Services reflect the status of operands beyond whether + they have been created + items: + properties: + namespace: + type: string + operatorName: + type: string + resources: + description: LastUpdateTime string `json:"lastTransitionTime,omitempty"` + items: + properties: + apiVersion: + type: string + kind: + type: string + managedResources: + description: Message string `json:"message,omitempty"` + items: + properties: + apiVersion: + type: string + kind: + type: string + namespace: + type: string + objectName: + type: string + status: + description: Type string `json:"type,omitempty"` + type: string + type: object + type: array + namespace: + type: string + objectName: + type: string + status: + type: string + type: object + type: array + status: + description: Type string `json:"type,omitempty"` + type: string + type: object + type: array + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null + +--- + + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + creationTimestamp: null + # labels: + # app.kubernetes.io/instance: operand-deployment-lifecycle-manager + # app.kubernetes.io/managed-by: operand-deployment-lifecycle-manager + # app.kubernetes.io/name: operand-deployment-lifecycle-manager + name: operatorconfigs.operator.ibm.com +spec: + group: operator.ibm.com + names: + kind: OperatorConfig + listKind: OperatorConfigList + plural: operatorconfigs + singular: operatorconfig + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - description: Current Phase + jsonPath: .status.phase + name: Phase + type: string + - jsonPath: .metadata.creationTimestamp + name: Created At + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: OperatorConfig is the Schema for the operatorconfigs API. Documentation + For additional details regarding install parameters check https://ibm.biz/icpfs39install. + License By installing this product you accept the license terms https://ibm.biz/icpfs39license + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: OperatorConfigSpec defines the desired state of OperatorConfig + properties: + foo: + description: Foo is an example field of OperatorConfig. Edit operatorconfig_types.go + to remove/update + type: string + services: + description: Services is a list of services to be configured, specifically + their operators + items: + description: ServiceOperatorConfig defines the configuration of + the service. + properties: + affinity: + description: If specified, the pod's scheduling constraints + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for + the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the + selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the + selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + x-kubernetes-map-type: atomic + weight: + description: Weight associated with matching the + corresponding nodeSelectorTerm, in the range + 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. + The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the + selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the + selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + x-kubernetes-map-type: atomic + type: array + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. + co-locate this pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of + label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of + label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules + (e.g. avoid putting this pod in the same node, zone, etc. + as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of + label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of + label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + name: + description: Name is the operator name as requested in the OperandRequest. + type: string + replicas: + description: |- + Number of desired pods. This is a pointer to distinguish between explicit + zero and not specified. Defaults to 1. + format: int32 + type: integer + topologySpreadConstraints: + description: |- + TopologySpreadConstraints describes how a group of pods ought to spread across topology + domains. Scheduler will schedule pods in a way which abides by the constraints. + All topologySpreadConstraints are ANDed. + items: + description: TopologySpreadConstraint specifies how to spread + matching pods among the given topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + maxSkew: + description: |- + MaxSkew describes the degree to which pods may be unevenly distributed. + When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference + between the number of matching pods in the target topology and the global minimum. + The global minimum is the minimum number of matching pods in an eligible domain + or zero if the number of eligible domains is less than MinDomains. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 2/2/1: + In this case, the global minimum is 1. + | zone1 | zone2 | zone3 | + | P P | P P | P | + - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; + scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) + violate MaxSkew(1). + - if MaxSkew is 2, incoming pod can be scheduled onto any zone. + When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence + to topologies that satisfy it. + It's a required field. Default value is 1 and 0 is not allowed. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + + + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + + + This is an alpha field and requires enabling MinDomainsInPodTopologySpread feature gate. + format: int32 + type: integer + topologyKey: + description: |- + TopologyKey is the key of node labels. Nodes that have a label with this key + and identical values are considered to be in the same topology. + We consider each as a "bucket", and try to put balanced number + of pods into each bucket. + We define a domain as a particular instance of a topology. + Also, we define an eligible domain as a domain whose nodes match the node selector. + e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. + And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. + It's a required field. + type: string + whenUnsatisfiable: + description: |- + WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy + the spread constraint. + - DoNotSchedule (default) tells the scheduler not to schedule it. + - ScheduleAnyway tells the scheduler to schedule the pod in any location, + but giving higher precedence to topologies that would help reduce the + skew. + A constraint is considered "Unsatisfiable" for an incoming pod + if and only if every possible node assignment for that pod would violate + "MaxSkew" on some topology. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 3/1/1: + | zone1 | zone2 | zone3 | + | P P P | P | P | + If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled + to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies + MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler + won't make it *more* imbalanced. + It's a required field. + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + x-kubernetes-list-map-keys: + - topologyKey + - whenUnsatisfiable + x-kubernetes-list-type: map + required: + - name + type: object + type: array + x-kubernetes-preserve-unknown-fields: true + type: object + x-kubernetes-preserve-unknown-fields: true + status: + description: OperatorConfigStatus defines the observed state of OperatorConfig + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/helm-cluster-scoped/values.yaml b/helm-cluster-scoped/values.yaml new file mode 100644 index 00000000..199e6e03 --- /dev/null +++ b/helm-cluster-scoped/values.yaml @@ -0,0 +1,11 @@ +# imagePullPrefix: icr.io +# imagePullSecret: ibm-entitlement-key + +# Note there are no leading or trailing /'s +imageRegistryNamespaceOperator: cpopen +imageRegistryNamespaceOperand: cpopen/cpfs + +# other configuration you think you might need for your operator +# following are examples, not required: +# operatorNamespace: "" +# servicesNamespace: "" diff --git a/helm/Chart.yaml b/helm/Chart.yaml new file mode 100644 index 00000000..c80dcabf --- /dev/null +++ b/helm/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: ibm-odlm +description: A Helm chart for ibm-odlm +type: application +version: 4.4.0 +appVersion: 4.4.0 \ No newline at end of file diff --git a/helm/templates/operator-deployment.yaml b/helm/templates/operator-deployment.yaml new file mode 100644 index 00000000..3c70f13e --- /dev/null +++ b/helm/templates/operator-deployment.yaml @@ -0,0 +1,258 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app.kubernetes.io/managed-by: Helm + productName: IBM_Cloud_Platform_Common_Services + name: operand-deployment-lifecycle-manager + namespace: {{ .Values.global.operatorNamespace }} +spec: + progressDeadlineSeconds: 600 + replicas: 1 + revisionHistoryLimit: 1 + selector: + matchLabels: + name: operand-deployment-lifecycle-manager + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + annotations: + alm-examples: |- + [ + { + "apiVersion": "operator.ibm.com/v1alpha1", + "kind": "OperandBindInfo", + "metadata": { + "labels": { + "app.kubernetes.io/instance": "operand-deployment-lifecycle-manager", + "app.kubernetes.io/managed-by": "operand-deployment-lifecycle-manager", + "app.kubernetes.io/name": "operand-deployment-lifecycle-manager" + }, + "name": "example-service" + }, + "spec": { + "bindings": { + "public": { + "configmap": "mongodb-configmap", + "secret": "mongodb-secret" + } + }, + "description": "Binding information that should be accessible to MongoDB adopters", + "operand": "mongodb-atlas-kubernetes", + "registry": "example-service" + } + }, + { + "apiVersion": "operator.ibm.com/v1alpha1", + "kind": "OperandConfig", + "metadata": { + "labels": { + "app.kubernetes.io/instance": "operand-deployment-lifecycle-manager", + "app.kubernetes.io/managed-by": "operand-deployment-lifecycle-manager", + "app.kubernetes.io/name": "operand-deployment-lifecycle-manager" + }, + "name": "example-service" + }, + "spec": { + "services": [ + { + "name": "jaeger", + "spec": { + "jaeger": { + "strategy": "allinone" + } + } + }, + { + "name": "mongodb-atlas-kubernetes", + "spec": { + "atlasDeployment": { + "deploymentSpec": { + "name": "test-deployment" + }, + "projectRef": { + "name": "my-project" + } + } + } + } + ] + } + }, + { + "apiVersion": "operator.ibm.com/v1alpha1", + "kind": "OperandRegistry", + "metadata": { + "labels": { + "app.kubernetes.io/instance": "operand-deployment-lifecycle-manager", + "app.kubernetes.io/managed-by": "operand-deployment-lifecycle-manager", + "app.kubernetes.io/name": "operand-deployment-lifecycle-manager" + }, + "name": "example-service" + }, + "spec": { + "operators": [ + { + "channel": "stable", + "installMode": "cluster", + "name": "jaeger", + "namespace": "default", + "packageName": "jaeger", + "sourceName": "community-operators", + "sourceNamespace": "openshift-marketplace" + }, + { + "channel": "stable", + "name": "mongodb-atlas-kubernetes", + "namespace": "default", + "packageName": "mongodb-atlas-kubernetes", + "sourceName": "community-operators", + "sourceNamespace": "openshift-marketplace" + } + ] + } + }, + { + "apiVersion": "operator.ibm.com/v1alpha1", + "kind": "OperandRequest", + "metadata": { + "labels": { + "app.kubernetes.io/instance": "operand-deployment-lifecycle-manager", + "app.kubernetes.io/managed-by": "operand-deployment-lifecycle-manager", + "app.kubernetes.io/name": "operand-deployment-lifecycle-manager" + }, + "name": "example-service" + }, + "spec": { + "requests": [ + { + "operands": [ + { + "name": "jaeger" + }, + { + "name": "mongodb-atlas-kubernetes" + } + ], + "registry": "example-service" + } + ] + } + } + ] + capabilities: Seamless Upgrades + categories: Developer Tools, Monitoring, Logging & Tracing, Security + certified: "false" + containerImage: icr.io/cpopen/odlm@sha256:3076710c9891bfb573ea4744398d02977702e8cf24e9f5151f9607c4dc2f95db + createdAt: "2024-09-28T19:55:35Z" + description: The Operand Deployment Lifecycle Manager provides a Kubernetes + CRD-based API to manage the lifecycle of operands. + nss.operator.ibm.com/managed-operators: ibm-odlm + # olm.operatorGroup: common-service + # olm.operatorNamespace: cs-op + # olm.skipRange: '>=1.2.0 <4.3.8' + # olm.targetNamespaces: cs-op + operatorframework.io/properties: '{"properties":[{"type":"olm.gvk","value":{"group":"operator.ibm.com","kind":"OperandBindInfo","version":"v1alpha1"}},{"type":"olm.gvk","value":{"group":"operator.ibm.com","kind":"OperandConfig","version":"v1alpha1"}},{"type":"olm.gvk","value":{"group":"operator.ibm.com","kind":"OperandRegistry","version":"v1alpha1"}},{"type":"olm.gvk","value":{"group":"operator.ibm.com","kind":"OperandRequest","version":"v1alpha1"}},{"type":"olm.gvk","value":{"group":"operator.ibm.com","kind":"OperatorConfig","version":"v1alpha1"}},{"type":"olm.package","value":{"packageName":"ibm-odlm","version":"4.4.0"}}]}' + operators.openshift.io/infrastructure-features: '["disconnected"]' + operators.operatorframework.io/builder: operator-sdk-v1.32.0 + operators.operatorframework.io/project_layout: go.kubebuilder.io/v3 + productID: 068a62892a1e4db39641342e592daa25 + productMetric: FREE + productName: IBM Cloud Platform Common Services + repository: https://github.com/IBM/operand-deployment-lifecycle-manager + support: IBM + creationTimestamp: null + labels: + app.kubernetes.io/managed-by: Helm + intent: projected + name: operand-deployment-lifecycle-manager + productName: IBM_Cloud_Platform_Common_Services + spec: + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: kubernetes.io/arch + operator: In + values: + - amd64 + - ppc64le + - s390x + containers: + - args: + - -v=1 + command: + - /manager + env: + - name: OPERATOR_NAMESPACE + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.namespace + - name: WATCH_NAMESPACE + valueFrom: + configMapKeyRef: + key: namespaces + name: namespace-scope + optional: true + - name: OPERATOR_CONDITION_NAME + value: operand-deployment-lifecycle-manager.v4.4.0 + - name: NO_OLM + value: "true" + image: {{ .Values.global.imagePullPrefix }}/{{ .Values.imageRegistryNamespaceOperator }}/odlm:4.4.0 + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 10 + httpGet: + path: /readyz + port: 8081 + scheme: HTTP + initialDelaySeconds: 120 + periodSeconds: 60 + successThreshold: 1 + timeoutSeconds: 10 + name: manager + readinessProbe: + failureThreshold: 10 + httpGet: + path: /healthz + port: 8081 + scheme: HTTP + initialDelaySeconds: 5 + periodSeconds: 20 + successThreshold: 1 + timeoutSeconds: 3 + resources: + limits: + cpu: 500m + memory: 512Mi + requests: + cpu: 200m + ephemeral-storage: 256Mi + memory: 200Mi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + imagePullSecrets: + - name: {{ .Values.global.imagePullSecret }} + dnsPolicy: ClusterFirst + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + serviceAccount: operand-deployment-lifecycle-manager + serviceAccountName: operand-deployment-lifecycle-manager + terminationGracePeriodSeconds: 10 diff --git a/helm/templates/rbac.yaml b/helm/templates/rbac.yaml new file mode 100644 index 00000000..90a7a1f9 --- /dev/null +++ b/helm/templates/rbac.yaml @@ -0,0 +1,222 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: operand-deployment-lifecycle-manager + namespace: {{ .Values.global.operatorNamespace }} +rules: +- apiGroups: + - "" + resources: + - configmaps + - namespaces + - secrets + - services + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - '*' + resources: + - '*' + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - k8s.keycloak.org + resources: + - keycloakrealmimports + - keycloaks + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - operator.ibm.com + resources: + - operandbindinfos + - operandbindinfos/finalizers + - operandbindinfos/status + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - operator.ibm.com + resources: + - operandconfigs + - operandconfigs/finalizers + - operandconfigs/status + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - operator.ibm.com + resources: + - operandregistries + - operandregistries/finalizers + - operandregistries/status + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - operator.ibm.com + resources: + - operandrequests + - operandrequests/finalizers + - operandrequests/status + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - operator.ibm.com + resources: + - operatorconfigs + - operatorconfigs/finalizers + - operatorconfigs/status + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - operators.coreos.com + resources: + - clusterserviceversions + - subscriptions + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - operators.coreos.com + resources: + - installplans + - operatorgroups + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - packages.operators.coreos.com + resources: + - packagemanifests + verbs: + - get + - list + - patch + - update + - watch +- apiGroups: + - route.openshift.io + resources: + - routes + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + +--- + +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: operand-deployment-lifecycle-manager + namespace: {{ .Values.global.operatorNamespace }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: operand-deployment-lifecycle-manager +subjects: +- kind: ServiceAccount + name: operand-deployment-lifecycle-manager + namespace: {{ .Values.global.operatorNamespace }} +--- + +apiVersion: v1 +kind: ServiceAccount +metadata: + name: operand-deployment-lifecycle-manager + namespace: {{ .Values.global.operatorNamespace }} + +--- +### role only for olm +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: operand-deployment-lifecycle-manager.v4.4.0 + namespace: {{ .Values.global.operatorNamespace }} +rules: +- apiGroups: + - operators.coreos.com + resourceNames: + - operand-deployment-lifecycle-manager.v4.4.0 + resources: + - operatorconditions + verbs: + - get + - update + - patch + +--- +### rolebinding only for olm +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: operand-deployment-lifecycle-manager.v4.4.0 + namespace: {{ .Values.global.operatorNamespace }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: operand-deployment-lifecycle-manager.v4.4.0 +subjects: +- kind: ServiceAccount + name: operand-deployment-lifecycle-manager + namespace: {{ .Values.global.operatorNamespace }} diff --git a/helm/values.yaml b/helm/values.yaml new file mode 100644 index 00000000..199e6e03 --- /dev/null +++ b/helm/values.yaml @@ -0,0 +1,11 @@ +# imagePullPrefix: icr.io +# imagePullSecret: ibm-entitlement-key + +# Note there are no leading or trailing /'s +imageRegistryNamespaceOperator: cpopen +imageRegistryNamespaceOperand: cpopen/cpfs + +# other configuration you think you might need for your operator +# following are examples, not required: +# operatorNamespace: "" +# servicesNamespace: "" diff --git a/main.go b/main.go index 8178733d..0f9b6dc0 100644 --- a/main.go +++ b/main.go @@ -21,6 +21,7 @@ import ( "os" "strings" + ocproute "github.com/openshift/api/route/v1" olmv1 "github.com/operator-framework/api/pkg/operators/v1" olmv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" operatorsv1 "github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/apis/operators/v1" @@ -38,17 +39,19 @@ import ( cache "github.com/IBM/controller-filtered-cache/filteredcache" nssv1 "github.com/IBM/ibm-namespace-scope-operator/api/v1" - operatorv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/api/v1alpha1" - "github.com/IBM/operand-deployment-lifecycle-manager/controllers/constant" - "github.com/IBM/operand-deployment-lifecycle-manager/controllers/k8sutil" - "github.com/IBM/operand-deployment-lifecycle-manager/controllers/namespacescope" - "github.com/IBM/operand-deployment-lifecycle-manager/controllers/operandbindinfo" - "github.com/IBM/operand-deployment-lifecycle-manager/controllers/operandconfig" - "github.com/IBM/operand-deployment-lifecycle-manager/controllers/operandregistry" - "github.com/IBM/operand-deployment-lifecycle-manager/controllers/operandrequest" - deploy "github.com/IBM/operand-deployment-lifecycle-manager/controllers/operator" - "github.com/IBM/operand-deployment-lifecycle-manager/controllers/operatorchecker" - "github.com/IBM/operand-deployment-lifecycle-manager/controllers/util" + operatorv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/v4/api/v1alpha1" + "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/constant" + "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/k8sutil" + "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/namespacescope" + "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/operandbindinfo" + "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/operandconfig" + "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/operandregistry" + "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/operandrequest" + "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/operandrequestnoolm" + deploy "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/operator" + "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/operatorchecker" + "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/operatorconfig" + "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/util" // +kubebuilder:scaffold:imports ) @@ -64,6 +67,7 @@ func init() { utilruntime.Must(operatorv1alpha1.AddToScheme(scheme)) utilruntime.Must(operatorsv1.AddToScheme(scheme)) + utilruntime.Must(ocproute.AddToScheme(scheme)) // +kubebuilder:scaffold:scheme } @@ -78,16 +82,16 @@ func main() { flag.BoolVar(&enableLeaderElection, "enable-leader-election", false, "Enable leader election for controller manager. "+ "Enabling this will ensure there is only one active controller manager.") - var stepSize = flag.Int("batch-chunk-size", 1, "batch-chunk-size is used to control at most how many subscriptions will be created concurrently") + var stepSize = flag.Int("batch-chunk-size", 3, "batch-chunk-size is used to control at most how many subscriptions will be created concurrently") flag.Parse() gvkLabelMap := map[schema.GroupVersionKind]cache.Selector{ corev1.SchemeGroupVersion.WithKind("Secret"): { - LabelSelector: constant.OpbiTypeLabel, + LabelSelector: constant.ODLMWatchedLabel, }, corev1.SchemeGroupVersion.WithKind("ConfigMap"): { - LabelSelector: constant.OpbiTypeLabel, + LabelSelector: constant.ODLMWatchedLabel, }, appsv1.SchemeGroupVersion.WithKind("Deployment"): { LabelSelector: constant.BindInfoRefreshLabel, @@ -120,12 +124,23 @@ func main() { klog.Errorf("unable to start manager: %v", err) os.Exit(1) } - if err = (&operandrequest.Reconciler{ - ODLMOperator: deploy.NewODLMOperator(mgr, "OperandRequest"), - StepSize: *stepSize, - }).SetupWithManager(mgr); err != nil { - klog.Errorf("unable to create controller OperandRequest: %v", err) - os.Exit(1) + noolm := util.GetNoOLM() + if noolm == "true" { + if err = (&operandrequestnoolm.Reconciler{ + ODLMOperator: deploy.NewODLMOperator(mgr, "OperandRequest"), + StepSize: *stepSize, + }).SetupWithManager(mgr); err != nil { + klog.Errorf("unable to create controller OperandRequestNoOLM: %v", err) + os.Exit(1) + } + } else { + if err = (&operandrequest.Reconciler{ + ODLMOperator: deploy.NewODLMOperator(mgr, "OperandRequest"), + StepSize: *stepSize, + }).SetupWithManager(mgr); err != nil { + klog.Errorf("unable to create controller OperandRequest: %v", err) + os.Exit(1) + } } if err = (&operandconfig.Reconciler{ ODLMOperator: deploy.NewODLMOperator(mgr, "OperandConfig"), @@ -164,6 +179,12 @@ func main() { } } } + if err = (&operatorconfig.Reconciler{ + ODLMOperator: deploy.NewODLMOperator(mgr, "OperatorConfig"), + }).SetupWithManager(mgr); err != nil { + klog.Error(err, "unable to create controller", "controller", "OperatorConfig") + os.Exit(1) + } // +kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("health", healthz.Ping); err != nil { diff --git a/test/e2e/constants.go b/test/e2e/constants.go index c3c0d576..43524132 100644 --- a/test/e2e/constants.go +++ b/test/e2e/constants.go @@ -54,7 +54,7 @@ const ( OperandConfigCrName = "common-service" // OperandBindInfoCrName specifies the name of the custom resource of the OperandBindInfo - OperandBindInfoCrName = "jenkins-public-bindinfo" + OperandBindInfoCrName = "mongodb-public-bindinfo" // OperandRequestNamespace1 specifies the namespace of the OperandRequest OperandRequestNamespace1 = "ibm-cloudpak-1" diff --git a/test/e2e/e2e_suite_test.go b/test/e2e/e2e_suite_test.go index 9e378dbd..dc584276 100644 --- a/test/e2e/e2e_suite_test.go +++ b/test/e2e/e2e_suite_test.go @@ -21,7 +21,6 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - "sigs.k8s.io/controller-runtime/pkg/envtest/printer" ) // These tests use Ginkgo (BDD-style Go testing framework). Refer to @@ -30,9 +29,8 @@ import ( func TestODLME2E(t *testing.T) { RegisterFailHandler(Fail) - RunSpecsWithDefaultAndCustomReporters(t, - "Operand Deployment Lifecycle Manager TestSuite", - []Reporter{printer.NewlineReporter{}}) + RunSpecs(t, + "Operand Deployment Lifecycle Manager TestSuite") } var _ = BeforeSuite(func(done Done) { diff --git a/test/e2e/helpers_test.go b/test/e2e/helpers_test.go index 275abbaa..24a5d10b 100644 --- a/test/e2e/helpers_test.go +++ b/test/e2e/helpers_test.go @@ -33,7 +33,7 @@ import ( "k8s.io/apimachinery/pkg/util/wait" "sigs.k8s.io/controller-runtime/pkg/client" - "github.com/IBM/operand-deployment-lifecycle-manager/api/v1alpha1" + "github.com/IBM/operand-deployment-lifecycle-manager/v4/api/v1alpha1" ) func createTestNamespace(namespace string) { @@ -295,8 +295,8 @@ func retrieveOperandConfig(obj client.Object, ns string) error { return nil } -// updateEtcdReplicas is used to update an OperandConfig instance -func updateEtcdReplicas(ns string) error { +// updateJaegerReplicas is used to update an OperandConfig instance +func updateJaegerStrategy(ns string) error { fmt.Println("--- UPDATE: OperandConfig Instance") con := &v1alpha1.OperandConfig{} if err := wait.PollImmediate(WaitForRetry, WaitForTimeout, func() (done bool, err error) { @@ -304,8 +304,10 @@ func updateEtcdReplicas(ns string) error { if err != nil { return false, client.IgnoreNotFound(err) } - con.Spec.Services[0].Spec = map[string]runtime.RawExtension{ - "etcdCluster": {Raw: []byte(`{"size": 3}`)}, + con.Spec.Services[0].Spec = map[string]v1alpha1.ExtensionWithMarker{ + "jaeger": { + RawExtension: runtime.RawExtension{Raw: []byte(`{"strategy": "allinone"}`)}, + }, } if err := k8sClient.Update(context.TODO(), con); err != nil { fmt.Println(" --- Waiting for OperandConfig instance stable ...") @@ -361,8 +363,8 @@ func retrieveOperandRegistry(obj client.Object, ns string) error { return nil } -// updateEtcdChannel is used to update the channel for the etcd operator -func updateEtcdChannel(ns string) error { +// updateJaegerChannel is used to update the channel for the jaeger operator +func updateJaegerChannel(ns string) error { fmt.Println("--- UPDATE: OperandRegistry Instance") reg := &v1alpha1.OperandRegistry{} if err := wait.PollImmediate(WaitForRetry, WaitForTimeout, func() (done bool, err error) { @@ -370,7 +372,7 @@ func updateEtcdChannel(ns string) error { if err != nil { return false, client.IgnoreNotFound(err) } - reg.Spec.Operators[0].Channel = "clusterwide-alpha" + reg.Spec.Operators[0].Channel = "stable" reg.Spec.Operators[0].InstallMode = "cluster" if err := k8sClient.Update(context.TODO(), reg); err != nil { fmt.Println(" --- Waiting for OperandRegistry instance stable ...") @@ -415,8 +417,8 @@ func checkNameSpaceandOperatorGroup(ns string) error { return nil } -// updateJenkinsScope is used to update the channel for the etcd operator -func updateJenkinsScope(ns string) error { +// updateMongodbScope is used to update the channel for the Mongodb operator +func updateMongodbScope(ns string) error { fmt.Println("--- UPDATE: OperandRegistry Instance") reg := &v1alpha1.OperandRegistry{} if err := wait.PollImmediate(WaitForRetry, WaitForTimeout, func() (done bool, err error) { @@ -488,7 +490,7 @@ func updateOperandBindInfo(ns string) (*v1alpha1.OperandBindInfo, error) { return false, err } secretCm := bi.Spec.Bindings["public"] - secretCm.Configmap = "jenkins-second-configmap" + secretCm.Configmap = "mongodb-second-configmap" bi.Spec.Bindings["public"] = secretCm if err := k8sClient.Update(context.TODO(), bi); err != nil { fmt.Println(" --- Waiting for OperandBindInfo instance stable ...") @@ -570,15 +572,15 @@ func retrieveSubscription(name, namespace string) (*olmv1alpha1.Subscription, er return obj, nil } -// retrieveJenkins is get a custom resource -func retrieveJenkins(name, namespace string) (*unstructured.Unstructured, error) { +// retrieveMongodb is get a custom resource +func retrieveMongodb(name, namespace string) (*unstructured.Unstructured, error) { obj := &unstructured.Unstructured{} - obj.SetGroupVersionKind(schema.GroupVersionKind{Group: "jenkins.io", Version: "v1alpha2", Kind: "Jenkins"}) + obj.SetGroupVersionKind(schema.GroupVersionKind{Group: "atlas.mongodb.com", Version: "v1", Kind: "AtlasDeployment"}) // Get subscription if err := wait.PollImmediate(WaitForRetry, WaitForTimeout, func() (done bool, err error) { if err = k8sClient.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: namespace}, obj); err != nil { if errors.IsNotFound(err) { - fmt.Println(" --- Waiting for custom resource Jenkins created ...") + fmt.Println(" --- Waiting for custom resource AtlasDeployment created ...") return false, nil } return false, err @@ -590,15 +592,15 @@ func retrieveJenkins(name, namespace string) (*unstructured.Unstructured, error) return obj, nil } -// retrieveEtcd is get a custom resource -func retrieveEtcd(name, namespace string) (*unstructured.Unstructured, error) { +// retrieveJaeger is get a custom resource +func retrieveJaeger(name, namespace string) (*unstructured.Unstructured, error) { obj := &unstructured.Unstructured{} - obj.SetGroupVersionKind(schema.GroupVersionKind{Group: "etcd.database.coreos.com", Version: "v1beta2", Kind: "EtcdCluster"}) + obj.SetGroupVersionKind(schema.GroupVersionKind{Group: "jaegertracing.io", Version: "v1", Kind: "Jaeger"}) // Get subscription if err := wait.PollImmediate(WaitForRetry, WaitForTimeout, func() (done bool, err error) { if err = k8sClient.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: namespace}, obj); err != nil { if errors.IsNotFound(err) { - fmt.Println(" --- Waiting for custom resource EtcdCluster created ...") + fmt.Println(" --- Waiting for custom resource Jaeger created ...") return false, nil } return false, err @@ -620,26 +622,29 @@ func newOperandConfigCR(name, namespace string) *v1alpha1.OperandConfig { Spec: v1alpha1.OperandConfigSpec{ Services: []v1alpha1.ConfigService{ { - Name: "etcd", - Spec: map[string]runtime.RawExtension{ - "etcdCluster": {Raw: []byte(`{"size": 1}`)}, + Name: "jaeger", + Spec: map[string]v1alpha1.ExtensionWithMarker{ + "jaeger": { + RawExtension: runtime.RawExtension{Raw: []byte(`{"strategy": "streaming"}`)}}, }, }, { - Name: "jenkins", - Spec: map[string]runtime.RawExtension{ - "jenkins": {Raw: []byte(`{"service":{"port": 8081}}`)}, + Name: "mongodb-atlas-kubernetes", + Spec: map[string]v1alpha1.ExtensionWithMarker{ + "atlasDeployment": { + RawExtension: runtime.RawExtension{Raw: []byte(`{"deploymentSpec":{"name": "test-deployment"}}`)}, + }, }, Resources: []v1alpha1.ConfigResource{ { - Name: "jenkins-configmap", + Name: "mongodb-configmap", APIVersion: "v1", Kind: "ConfigMap", Labels: map[string]string{ - "jenkins": "configmap", + "mongodb": "configmap", }, Annotations: map[string]string{ - "jenkins": "configmap", + "mongodb": "configmap", }, Data: &runtime.RawExtension{ Raw: []byte(`{"data": {"port": "8081"}}`), @@ -647,14 +652,14 @@ func newOperandConfigCR(name, namespace string) *v1alpha1.OperandConfig { Force: false, }, { - Name: "jenkins-second-configmap", + Name: "mongodb-second-configmap", APIVersion: "v1", Kind: "ConfigMap", Labels: map[string]string{ - "jenkins": "configmap", + "mongodb": "configmap", }, Annotations: map[string]string{ - "jenkins": "configmap", + "mongodb": "configmap", }, Data: &runtime.RawExtension{ Raw: []byte(`{"data": {"port": "8080"}}`), @@ -662,14 +667,14 @@ func newOperandConfigCR(name, namespace string) *v1alpha1.OperandConfig { Force: false, }, { - Name: "jenkins-secret", + Name: "mongodb-secret", APIVersion: "v1", Kind: "Secret", Labels: map[string]string{ - "jenkins": "secret", + "mongodb": "secret", }, Annotations: map[string]string{ - "jenkins": "secret", + "mongodb": "secret", }, Data: &runtime.RawExtension{ Raw: []byte(`{"type": "Opaque", "data": {"password": "UyFCXCpkJHpEc2I9", "username": "YWRtaW4="}}`), @@ -693,21 +698,21 @@ func newOperandRegistryCR(name, namespace, OperatorNamespace string) *v1alpha1.O Spec: v1alpha1.OperandRegistrySpec{ Operators: []v1alpha1.Operator{ { - Name: "etcd", + Name: "jaeger", Namespace: OperatorNamespace, SourceName: "community-operators", SourceNamespace: "openshift-marketplace", - PackageName: "etcd", - Channel: "singlenamespace-alpha", + PackageName: "jaeger", + Channel: "stable", Scope: v1alpha1.ScopePublic, }, { - Name: "jenkins", + Name: "mongodb-atlas-kubernetes", Namespace: OperatorNamespace, SourceName: "community-operators", SourceNamespace: "openshift-marketplace", - PackageName: "jenkins-operator", - Channel: "alpha", + PackageName: "mongodb-atlas-kubernetes", + Channel: "stable", }, }, }, @@ -724,21 +729,21 @@ func newOperandRegistryCRforKind(name, namespace, OperatorNamespace string) *v1a Spec: v1alpha1.OperandRegistrySpec{ Operators: []v1alpha1.Operator{ { - Name: "etcd", + Name: "jaeger", Namespace: OperatorNamespace, SourceName: "operatorhubio-catalog", SourceNamespace: "olm", - PackageName: "etcd", - Channel: "singlenamespace-alpha", + PackageName: "jaeger", + Channel: "stable", Scope: v1alpha1.ScopePublic, }, { - Name: "jenkins", + Name: "mongodb-atlas-kubernetes", Namespace: OperatorNamespace, SourceName: "operatorhubio-catalog", SourceNamespace: "olm", - PackageName: "jenkins-operator", - Channel: "alpha", + PackageName: "mongodb-atlas-kubernetes", + Channel: "stable", }, }, }, @@ -759,10 +764,10 @@ func newOperandRequestWithoutBindinfo(name, namespace, RegistryNamespace string) RegistryNamespace: RegistryNamespace, Operands: []v1alpha1.Operand{ { - Name: "etcd", + Name: "jaeger", }, { - Name: "jenkins", + Name: "mongodb-atlas-kubernetes", }, }, }, @@ -785,11 +790,11 @@ func newOperandRequestWithBindinfo(name, namespace, RegistryNamespace string) *v RegistryNamespace: RegistryNamespace, Operands: []v1alpha1.Operand{ { - Name: "jenkins", - Bindings: map[string]v1alpha1.SecretConfigmap{ + Name: "mongodb-atlas-kubernetes", + Bindings: map[string]v1alpha1.Bindable{ "public": { - Secret: "jenkins-secret", - Configmap: "jenkins-configmap", + Secret: "mongodb-secret", + Configmap: "mongodb-configmap", }, }, }, @@ -808,14 +813,14 @@ func newOperandBindInfoCR(name, namespace, RegistryNamespace string) *v1alpha1.O Namespace: namespace, }, Spec: v1alpha1.OperandBindInfoSpec{ - Operand: "jenkins", + Operand: "mongodb-atlas-kubernetes", Registry: OperandRegistryCrName, RegistryNamespace: RegistryNamespace, - Bindings: map[string]v1alpha1.SecretConfigmap{ + Bindings: map[string]v1alpha1.Bindable{ "public": { - Secret: "jenkins-secret", - Configmap: "jenkins-configmap", + Secret: "mongodb-secret", + Configmap: "mongodb-configmap", }, }, }, diff --git a/test/e2e/odlm_test.go b/test/e2e/odlm_test.go index 44d99254..ee0993c1 100644 --- a/test/e2e/odlm_test.go +++ b/test/e2e/odlm_test.go @@ -20,7 +20,7 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - operatorv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/api/v1alpha1" + operatorv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/v4/api/v1alpha1" ) // +kubebuilder:docs-gen:collapse=Imports @@ -67,19 +67,19 @@ var _ = Describe("Testing ODLM", func() { // Check if the subscription is created By("Check if the subscription is created") - sub, err := retrieveSubscription("etcd", OperatorNamespace) + sub, err := retrieveSubscription("jaeger", OperatorNamespace) Expect(err).ToNot(HaveOccurred()) Expect(sub).ToNot(BeNil()) // Check if the custom resource is created By("Check if the custom resource is created") - etcdCluster, err := retrieveEtcd("example", OperandRegistryNamespace) + JaegerCR, err := retrieveJaeger("my-jaeger", OperandRegistryNamespace) Expect(err).ToNot(HaveOccurred()) - Expect(etcdCluster).ToNot(BeNil()) + Expect(JaegerCR).ToNot(BeNil()) - // Update the Jenkins operator to the public scope - By("Update the Jenkins operator to the public scope") - err = updateJenkinsScope(OperandRegistryNamespace) + // Update the Mongodb operator to the public scope + By("Update the Mongodb operator to the public scope") + err = updateMongodbScope(OperandRegistryNamespace) Expect(err).ToNot(HaveOccurred()) // Check the status of the OperandRequest @@ -97,34 +97,34 @@ var _ = Describe("Testing ODLM", func() { // Check if the subscription is created By("Check if the subscription is created") - sub, err = retrieveSubscription("jenkins", OperatorNamespace) + sub, err = retrieveSubscription("mongodb-atlas-kubernetes", OperatorNamespace) Expect(err).ToNot(HaveOccurred()) Expect(sub).ToNot(BeNil()) // Check if the custom resource is created By("Check if the custom resource is created") - jenkins, err := retrieveJenkins("example", OperandRegistryNamespace) + mongodb, err := retrieveMongodb("my-atlas-deployment", OperandRegistryNamespace) Expect(err).ToNot(HaveOccurred()) - Expect(jenkins).ToNot(BeNil()) + Expect(mongodb).ToNot(BeNil()) // Check if the k8s resource is created By("Check if the k8s resource is created") - jenkinsConfigmap, err := retrieveConfigmap("jenkins-configmap", OperandRegistryNamespace) + mongodbConfigmap, err := retrieveConfigmap("mongodb-configmap", OperandRegistryNamespace) Expect(err).ToNot(HaveOccurred()) - Expect(jenkinsConfigmap).ToNot(BeNil()) + Expect(mongodbConfigmap).ToNot(BeNil()) // Update the OperandConfig By("Update the OperandConfig") Eventually(func() bool { - err = updateEtcdReplicas(OperandRegistryNamespace) + err = updateJaegerStrategy(OperandRegistryNamespace) if err != nil { return false } - etcdCluster, err := retrieveEtcd("example", OperandRegistryNamespace) + jaegerCR, err := retrieveJaeger("my-jaeger", OperandRegistryNamespace) if err != nil { return false } - if etcdCluster.Object["spec"].(map[string]interface{})["size"].(int64) != 3 { + if jaegerCR.Object["spec"].(map[string]interface{})["strategy"].(string) != "allinone" { return false } return true @@ -144,7 +144,7 @@ var _ = Describe("Testing ODLM", func() { By("Wait the OperandRegistry is running") reg, err = waitRegistryStatus(operatorv1alpha1.RegistryRunning) Expect(err).ToNot(HaveOccurred()) - Expect(len(reg.Status.OperatorsStatus["jenkins"].ReconcileRequests)).Should(Equal(1)) + Expect(len(reg.Status.OperatorsStatus["mongodb-atlas-kubernetes"].ReconcileRequests)).Should(Equal(1)) // Create the second OperandRequest instance By("Create the second OperandRequest instance") @@ -162,7 +162,7 @@ var _ = Describe("Testing ODLM", func() { By("Wait the OperandRegistry is running") reg, err = waitRegistryStatus(operatorv1alpha1.RegistryRunning) Expect(err).ToNot(HaveOccurred()) - Expect(len(reg.Status.OperatorsStatus["jenkins"].ReconcileRequests)).Should(Equal(2)) + Expect(len(reg.Status.OperatorsStatus["mongodb-atlas-kubernetes"].ReconcileRequests)).Should(Equal(2)) bi, err = waitBindInfoStatus(operatorv1alpha1.BindInfoCompleted, OperatorNamespace) Expect(err).ToNot(HaveOccurred()) @@ -170,11 +170,11 @@ var _ = Describe("Testing ODLM", func() { // Check if the secret and configmap are copied By("Check if the secret and configmap are copied") - sec, err := retrieveSecret("jenkins-secret", OperandRequestNamespace2) + sec, err := retrieveSecret("mongodb-secret", OperandRequestNamespace2) Expect(err).ToNot(HaveOccurred()) Expect(sec).ToNot(BeNil()) - cm, err := retrieveConfigmap("jenkins-configmap", OperandRequestNamespace2) + cm, err := retrieveConfigmap("mongodb-configmap", OperandRequestNamespace2) Expect(err).ToNot(HaveOccurred()) Expect(cm).ToNot(BeNil()) @@ -184,13 +184,13 @@ var _ = Describe("Testing ODLM", func() { Expect(err).ToNot(HaveOccurred()) Expect(cm).ToNot(BeNil()) - cm, err = retrieveConfigmap("jenkins-public-bindinfo-jenkins-second-configmap", OperandRequestNamespace1) + cm, err = retrieveConfigmap("mongodb-public-bindinfo-mongodb-second-configmap", OperandRequestNamespace1) Expect(err).ToNot(HaveOccurred()) Expect(cm).ToNot(BeNil()) // Delete the last operator and related operands from the first OperandRequest By("Delete the last operator and related operands from the first OperandRequest") - req1, err = absentOperandFromRequest(OperandRequestNamespace1, "jenkins") + req1, err = absentOperandFromRequest(OperandRequestNamespace1, "mongodb-atlas-kubernetes") Expect(err).ToNot(HaveOccurred()) Expect(len(req1.Spec.Requests[0].Operands)).Should(Equal(1)) @@ -202,11 +202,11 @@ var _ = Describe("Testing ODLM", func() { By("Check the status of the OperandRegistry") reg, err = waitRegistryStatus(operatorv1alpha1.RegistryRunning) Expect(err).ToNot(HaveOccurred()) - Expect(len(reg.Status.OperatorsStatus["jenkins"].ReconcileRequests)).Should(Equal(1)) + Expect(len(reg.Status.OperatorsStatus["mongodb-atlas-kubernetes"].ReconcileRequests)).Should(Equal(1)) // Add an operator into the first OperandRequest By("Add an operator into the first OperandRequest") - req1, err = presentOperandFromRequest(OperandRequestNamespace1, "jenkins") + req1, err = presentOperandFromRequest(OperandRequestNamespace1, "mongodb-atlas-kubernetes") Expect(err).ToNot(HaveOccurred()) Expect(len(req1.Spec.Requests[0].Operands)).Should(Equal(2)) @@ -217,7 +217,7 @@ var _ = Describe("Testing ODLM", func() { By("Check the status of the OperandRegistry") reg, err = waitRegistryStatus(operatorv1alpha1.RegistryRunning) Expect(err).ToNot(HaveOccurred()) - Expect(len(reg.Status.OperatorsStatus["jenkins"].ReconcileRequests)).Should(Equal(2)) + Expect(len(reg.Status.OperatorsStatus["mongodb-atlas-kubernetes"].ReconcileRequests)).Should(Equal(2)) // Delete the second OperandRequest By("Delete the second OperandRequest") @@ -227,7 +227,7 @@ var _ = Describe("Testing ODLM", func() { By("Check the status of the OperandRegistry") reg, err = waitRegistryStatus(operatorv1alpha1.RegistryRunning) Expect(err).ToNot(HaveOccurred()) - Expect(len(reg.Status.OperatorsStatus["jenkins"].ReconcileRequests)).Should(Equal(1)) + Expect(len(reg.Status.OperatorsStatus["mongodb-atlas-kubernetes"].ReconcileRequests)).Should(Equal(1)) // Delete the first OperandRequest By("Delete the first OperandRequest") @@ -236,12 +236,12 @@ var _ = Describe("Testing ODLM", func() { // Check if the k8s resource is deleted By("Check if the k8s resource is deleted") - err = waitConfigmapDeletion("jenkins-configmap", OperandRegistryNamespace) + err = waitConfigmapDeletion("mongodb-configmap", OperandRegistryNamespace) Expect(err).ToNot(HaveOccurred()) - // Update the channel of the etcd operator - By("Update the channel of the etcd operator") - err = updateEtcdChannel(OperandRegistryNamespace) + // Update the channel of the jaeger operator + By("Update the channel of the jaeger operator") + err = updateJaegerChannel(OperandRegistryNamespace) Expect(err).ToNot(HaveOccurred()) // Create the first OperandRequest @@ -258,7 +258,7 @@ var _ = Describe("Testing ODLM", func() { // Check if the subscription is created By("Check if the subscription is created") - sub, err = retrieveSubscription("etcd", "openshift-operators") + sub, err = retrieveSubscription("jaeger", "openshift-operators") Expect(err).ToNot(HaveOccurred()) Expect(sub).ToNot(BeNil()) diff --git a/test/e2e/utils_test.go b/test/e2e/utils_test.go index 8691b5c2..7548b0f0 100644 --- a/test/e2e/utils_test.go +++ b/test/e2e/utils_test.go @@ -19,7 +19,7 @@ package e2e import ( "strings" - etcdv1beta2 "github.com/coreos/etcd-operator/pkg/apis/etcd/v1beta2" + jaegerv1 "github.com/jaegertracing/jaeger-operator/apis/v1" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" olmv1 "github.com/operator-framework/api/pkg/operators/v1" @@ -34,7 +34,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log/zap" kbtestutils "sigs.k8s.io/kubebuilder/test/e2e/utils" - apiv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/api/v1alpha1" + apiv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/v4/api/v1alpha1" ) var ( @@ -70,7 +70,7 @@ func initSuite() { Expect(err).NotTo(HaveOccurred()) err = olmv1.AddToScheme(clientgoscheme.Scheme) Expect(err).NotTo(HaveOccurred()) - err = etcdv1beta2.AddToScheme(clientgoscheme.Scheme) + err = jaegerv1.AddToScheme(clientgoscheme.Scheme) Expect(err).NotTo(HaveOccurred()) k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{ diff --git a/triggerfile b/triggerfile new file mode 100644 index 00000000..03e90c99 --- /dev/null +++ b/triggerfile @@ -0,0 +1 @@ +20240424-1440 diff --git a/version/version.go b/version/version.go index d1b00d23..95f3117b 100644 --- a/version/version.go +++ b/version/version.go @@ -17,5 +17,5 @@ package version var ( - Version = "4.0.0" + Version = "4.4.0" )