From 421d44a359acfc3fbc6781183feed2dbc0d7a840 Mon Sep 17 00:00:00 2001 From: "Joshua T. Fox" <4542591+JoshuaFox@users.noreply.github.com> Date: Sun, 24 Nov 2019 13:40:01 +0200 Subject: [PATCH 001/120] Correct git repo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c699d87..8b158de 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ gcloud container clusters get-credentials "$CLUSTER" --zone "$ZONE" Clone this repo and the associated tools repo. ```shell -git clone https://github.com/GoogleCloudPlatform/redis-enterprise-k8s-docs.git +git clone https://github.com/RedisLabs/redis-enterprise-k8s-docs.git ``` #### Install the Application resource definition From 0da73c21c7872549ed0acbde166eb0a8041eac35 Mon Sep 17 00:00:00 2001 From: "Joshua T. Fox" <4542591+JoshuaFox@users.noreply.github.com> Date: Sun, 24 Nov 2019 16:14:33 +0200 Subject: [PATCH 002/120] wrong dir name --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c699d87..b20c2b2 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ community. The source code can be found on Navigate to the `marketplace` directory: ```shell -cd gcpmarketplace +cd gkemarketplace ``` #### Configure the app with environment variables From e2f90ea94e545e5fb7a10b7f5ca05a5b64d2abc9 Mon Sep 17 00:00:00 2001 From: "Joshua T. Fox" <4542591+JoshuaFox@users.noreply.github.com> Date: Sun, 24 Nov 2019 17:32:00 +0200 Subject: [PATCH 003/120] small fixes --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c699d87..bf01888 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ community. The source code can be found on ### Install the Application -Navigate to the `marketplace` directory: +Navigate to the `gkemarketplace` directory: ```shell cd gcpmarketplace @@ -185,6 +185,8 @@ expanded manifest file for future updates to the application. | envsubst '$APP_INSTANCE_NAME $NAMESPACE $IMAGE_REDIS $REPLICAS $REDIS_ADMIN $SERVICE_ACCOUNT $IMAGE_UBBAGENT $NODE_CPU $NODE_MEM' \ > "${APP_INSTANCE_NAME}_manifest.yaml" ``` +#### Supply secrets +This entire process will not work unless you provide the secrets. #### Apply the manifest to your Kubernetes cluster @@ -193,6 +195,9 @@ Use `kubectl` to apply the manifest to your Kubernetes cluster: ```shell # rbac.yaml kubectl apply -f "${APP_INSTANCE_NAME}_rbac.yaml" --namespace "${NAMESPACE}" + +kubectl apply -f deployer/crd.yaml + # manifest.yaml kubectl apply -f "${APP_INSTANCE_NAME}_manifest.yaml" --namespace "${NAMESPACE}" ``` From 683ca13ed08ed87915aa4a57792d8660e0a31a7b Mon Sep 17 00:00:00 2001 From: Joshua Fox <4542591+JoshuaFox@users.noreply.github.com> Date: Mon, 25 Nov 2019 16:10:40 +0200 Subject: [PATCH 004/120] Upgrade version of our package to 1.10 and use newer ubb image --- .gitignore | 1 + Makefile | 6 +++--- README.md | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..30bcfa4 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.build/ diff --git a/Makefile b/Makefile index 88b3d86..21beb16 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ include helper/gcloud.Makefile include helper/var.Makefile -TAG ?= 1.9 +TAG ?= 1.10 REGISTRY ?= gcr.io/proven-reality-226706 METRICS_EXPORTER_TAG ?= v0.7.1 @@ -56,8 +56,8 @@ app/build:: .build/redislabs/deployer \ .build/redislabs/redislabs: .build/var/REGISTRY \ .build/var/TAG \ | .build/redislabs - docker pull redislabs/operator:498_f987b08 - docker tag redislabs/operator:498_f987b08 \ + docker pull redislabs/operator:5.4.6-1186 + docker tag redislabs/operator:5.4.6-1186 \ "$(REGISTRY)/redislabs:$(TAG)" docker push "$(REGISTRY)/redislabs:$(TAG)" @touch "$@" diff --git a/README.md b/README.md index c699d87..1a3d97c 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,7 @@ export NODE_MEM=1 Configure the container images: ```shell -export IMAGE_REDIS=redislabs/operator:498_f987b08 +export IMAGE_REDIS=redislabs/operator:5.4.6-1186 export IMAGE_UBBAGENT=gcr.io/proven-reality-226706/redislabs/ubbagent:1.9 ``` From 98798b01d7026b256d59d0a159a7a1af9b7ece38 Mon Sep 17 00:00:00 2001 From: "Joshua T. Fox" <4542591+JoshuaFox@users.noreply.github.com> Date: Tue, 26 Nov 2019 11:07:00 +0200 Subject: [PATCH 005/120] rbac.yaml.template is in manifest/ not scripts/ --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b20c2b2..0aeb284 100644 --- a/README.md +++ b/README.md @@ -175,7 +175,7 @@ expanded manifest file for future updates to the application. # Define name of service account export SERVICE_ACCOUNT=redis-enterprise-operator # Expand rbac.yaml.template - envsubst '$APP_INSTANCE_NAME $NAMESPACE $SERVICE_ACCOUNT' < scripts/rbac.yaml.template > "${APP_INSTANCE_NAME}_rbac.yaml" + envsubst '$APP_INSTANCE_NAME $NAMESPACE $SERVICE_ACCOUNT' < manifest/rbac.yaml.template > "${APP_INSTANCE_NAME}_rbac.yaml" ``` 1. Expand `Application`/`crd`/`operator`/`ConfigMap` YAML files. From 48f1b7024f3d421748c4d33da5f5fca78ef34c73 Mon Sep 17 00:00:00 2001 From: "Joshua T. Fox" <4542591+JoshuaFox@users.noreply.github.com> Date: Tue, 26 Nov 2019 11:10:12 +0200 Subject: [PATCH 006/120] Must apply rd.yaml --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 0aeb284..d4d78e5 100644 --- a/README.md +++ b/README.md @@ -174,6 +174,7 @@ expanded manifest file for future updates to the application. ```shell # Define name of service account export SERVICE_ACCOUNT=redis-enterprise-operator + # Expand rbac.yaml.template envsubst '$APP_INSTANCE_NAME $NAMESPACE $SERVICE_ACCOUNT' < manifest/rbac.yaml.template > "${APP_INSTANCE_NAME}_rbac.yaml" ``` @@ -193,6 +194,10 @@ Use `kubectl` to apply the manifest to your Kubernetes cluster: ```shell # rbac.yaml kubectl apply -f "${APP_INSTANCE_NAME}_rbac.yaml" --namespace "${NAMESPACE}" + +# Custom Resource Definition +kubectl apply -f deployer/crd.yaml + # manifest.yaml kubectl apply -f "${APP_INSTANCE_NAME}_manifest.yaml" --namespace "${NAMESPACE}" ``` From 3d7d07912f0b2ff91bf2e7257117fa9df18facce Mon Sep 17 00:00:00 2001 From: "Joshua T. Fox" <4542591+JoshuaFox@users.noreply.github.com> Date: Tue, 26 Nov 2019 16:26:47 +0200 Subject: [PATCH 007/120] Edit the Readme for content and style --- README.md | 71 +++++++++++++++++++++++++------------------------------ 1 file changed, 32 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index a91abce..e35e037 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Overview -Redis-Enterprise is + -[Learn more](https://www.redislabs.com/). +This bundles [Redis Enterprise](https://www.redislabs.com/) into a form suited to Google Cloud Platform Marketplace. ## Design @@ -11,26 +11,28 @@ Redis-Enterprise is ### Solution Information -Redis-Enterprise cluster is deployed within a Kubernetes `StatefulSet`. +Redis-Enterprise cluster is deployed within a Kubernetes StatefulSet. The deployment creates two services: -- client-facing one, designed to be used for client connections to the Redis-Enterprise +- A client-facing one, designed to be used for client connections to the Redis-Enterprise cluster with port forwarding or using a LoadBalancer, - and service discovery - a headless service for connections between the Redis-Enterprise nodes. -Redis-Enterprise K8s application has the following ports configured: +Redis-Enterprise Kubernetes application has the following ports configured: [TODO] # Installation ## Quick install with Google Cloud Marketplace -Get up and running with a few clicks! Install this Redis-Enterprise app to a +Get up and running with a few clicks! Install this Redis Enterprise app to a Google Kubernetes Engine cluster using Google Cloud Marketplace. ## Command line instructions +For testing, you may want to deploy straight from your command line, "simulating" what Marketplace does when it deploys. This is not a perfect simulation as the environment differs. For example, this entire process will not work unless you provide the [secrets](https://kubernetes.io/docs/concepts/configuration/secret/). + ### Prerequisites @@ -51,7 +53,7 @@ gcloud auth configure-docker #### Create a Google Kubernetes Engine cluster -Create a new cluster from the command line. +Create a new cluster from the command line. The command is idempotent so runs after the first are not needed, but do no harm. ```shell export CLUSTER=redis-cluster @@ -76,25 +78,23 @@ git clone https://github.com/RedisLabs/redis-enterprise-k8s-docs.git #### Install the Application resource definition -An Application resource is a collection of individual Kubernetes components, +An Application resource is an addition to the Kubernetes metamodel: A collection of individual Kubernetes components, such as Services, Deployments, and so on, that you can manage as a group. -To set up your cluster to understand Application resources, run the following command: - -```shell -kubectl apply -f "https://raw.githubusercontent.com/GoogleCloudPlatform/marketplace-k8s-app-tools/master/crd/app-crd.yaml" -``` - -You need to run this command once. - The Application resource is defined by the [Kubernetes SIG-apps](https://github.com/kubernetes/community/tree/master/sig-apps) community. The source code can be found on [github.com/kubernetes-sigs/application](https://github.com/kubernetes-sigs/application). +To add Application to the metamodel and thus set up your cluster to understand Application resources, run the following command. It is idempotent and so need to be run only once; subsequent runs do nothing. + +```shell +kubectl apply -f "https://raw.githubusercontent.com/GoogleCloudPlatform/marketplace-k8s-app-tools/master/crd/app-crd.yaml" +``` + ### Install the Application -Navigate to the `gkemarketplace` directory: +Go to the `gkemarketplace` directory: ```shell cd gkemarketplace @@ -104,7 +104,7 @@ cd gkemarketplace Choose an instance name and [namespace](https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/) -for the app. In most cases, you can use the `default` namespace. +for the app as follows. In most cases, you can use the `default` namespace. ```shell export APP_INSTANCE_NAME=redis-labs-1 @@ -117,12 +117,11 @@ Set the number of replicas: export REPLICAS=3 ``` - Set the username for the app: ```shell export REDIS_ADMIN=admin@acme.com -``` +`` Set the CPU and Memory for nodes: @@ -142,7 +141,7 @@ export IMAGE_UBBAGENT=gcr.io/proven-reality-226706/redislabs/ubbagent:1.9 #### Create namespace in your Kubernetes cluster -If you use a different namespace than the `default`, run the command below to create a new namespace: +Run the command below to create a new namespace. It is idempotent. ```shell kubectl create namespace "$NAMESPACE" @@ -150,9 +149,9 @@ kubectl create namespace "$NAMESPACE" #### Prerequisites for using Role-Based Access Control -If you want to use [role-based access control](https://kubernetes.io/docs/reference/access-authn-authz/rbac/) -for the app, you must grant your user the ability to create roles in -Kubernetes by running the following command: +To use [role-based access control](https://kubernetes.io/docs/reference/access-authn-authz/rbac/) +for the app, grant your user the ability to create roles in +Kubernetes by running the following idempotent command: ```shell kubectl create clusterrolebinding cluster-admin-binding \ @@ -160,7 +159,6 @@ kubectl create clusterrolebinding cluster-admin-binding \ --user $(gcloud config get-value account) ``` -You need to run this command **once** for the cluster. For steps to enable role-based access control in Google Kubernetes Engine, see the [Kubernetes Engine documentation](https://cloud.google.com/kubernetes-engine/docs/how-to/role-based-access-control). @@ -169,7 +167,7 @@ the [Kubernetes Engine documentation](https://cloud.google.com/kubernetes-engine Use `envsubst` to expand the template. We recommend that you save the expanded manifest file for future updates to the application. -1. Expand `RBAC` YAML file. You must configure RBAC related stuff . +1. Expand `RBAC` YAML file. You must configure RBAC related stuff. ```shell # Define name of service account @@ -186,8 +184,6 @@ expanded manifest file for future updates to the application. | envsubst '$APP_INSTANCE_NAME $NAMESPACE $IMAGE_REDIS $REPLICAS $REDIS_ADMIN $SERVICE_ACCOUNT $IMAGE_UBBAGENT $NODE_CPU $NODE_MEM' \ > "${APP_INSTANCE_NAME}_manifest.yaml" ``` -#### Supply secrets -This entire process will not work unless you provide the secrets. #### Apply the manifest to your Kubernetes cluster @@ -198,8 +194,7 @@ Use `kubectl` to apply the manifest to your Kubernetes cluster: kubectl apply -f "${APP_INSTANCE_NAME}_rbac.yaml" --namespace "${NAMESPACE}" -# Custom Resource Definition - +# crd.yaml: Custom Resource Definition kubectl apply -f deployer/crd.yaml # manifest.yaml @@ -208,19 +203,17 @@ kubectl apply -f "${APP_INSTANCE_NAME}_manifest.yaml" --namespace "${NAMESPACE}" #### View the app in the Google Cloud Platform Console -To get the Console URL for your app, run the following command: +Get the Goocle Cloud Console URL for your app, then open this URL in your browser: ```shell echo "https://console.cloud.google.com/kubernetes/application/${ZONE}/${CLUSTER}/${NAMESPACE}/${APP_INSTANCE_NAME}" ``` -To view your app, open the URL in your browser. - #### Get the status of the cluster By default, the application does not have an external IP address. To get the status of the cluster, use `kubectl port-forward` to access the dashboard on the master -node: +node at `localhost`. ``` kubectl port-forward redis-enterprise-cluster-0 8443 @@ -229,7 +222,7 @@ kubectl port-forward redis-enterprise-cluster-0 8443 #### Getting the Admin Password -See instructions here: https://docs.redislabs.com/latest/rs/faqs/ +See [instructions here](https://docs.redislabs.com/latest/rs/faqs/) #### Access the Redis-Enterprise service externally @@ -237,7 +230,7 @@ See instructions here: https://docs.redislabs.com/latest/rs/faqs/ kubectl get services -n $NAMESPACE ``` -> **NOTE:** It might take some time for the external IP to be provisioned. +**NOTE:** It might take some time for the external IP to be provisioned. # Uninstall the Application @@ -263,7 +256,7 @@ export NAMESPACE=default ### Delete the resources -> **NOTE:** We recommend to use a kubectl version that is the same as the version of your cluster. Using the same versions of kubectl and the cluster helps avoid unforeseen issues. +> **NOTE:** We recommend to use a kubectl version that is the same as the version of your cluster to avoid unforeseen issues. To delete the resources, use the expanded manifest file used for the installation. @@ -277,7 +270,7 @@ kubectl delete -f "${APP_INSTANCE_NAME}_manifest.yaml" --namespace "${NAMESPACE} kubectl delete -f "${APP_INSTANCE_NAME}_rbac.yaml" --namespace "${NAMESPACE}" ``` -Otherwise, delete the resources using types and a label: +Alternatively, delete the resources using types and a label: ```shell kubectl delete statefulset,secret,service,configmap,serviceaccount,role,rolebinding,application \ @@ -292,7 +285,7 @@ PersistentVolumeClaims that were attached to their Pods. This prevents your installations from accidentally deleting stateful data. To remove the PersistentVolumeClaims with their attached persistent disks, run -the following `kubectl` commands: +the following commands: ```shell for pv in $(kubectl get pvc --namespace $NAMESPACE \ From f4592bf5a96c17522c811593f75520dac898f171 Mon Sep 17 00:00:00 2001 From: "Joshua T. Fox" <4542591+JoshuaFox@users.noreply.github.com> Date: Tue, 26 Nov 2019 16:29:27 +0200 Subject: [PATCH 008/120] Fix redis image registry url --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e35e037..bc6e8cb 100644 --- a/README.md +++ b/README.md @@ -132,10 +132,10 @@ export NODE_MEM=1 ``` -Configure the container images: +Configure the container images. Note that these version numbers might not be up to date. ```shell -export IMAGE_REDIS=redislabs/operator:498_f987b08 +export IMAGE_REDIS=gcr.io/proven-reality-226706/redislabs:1.10 export IMAGE_UBBAGENT=gcr.io/proven-reality-226706/redislabs/ubbagent:1.9 ``` From 7515cb995a77bbadb29c02865d41c41ae933b799 Mon Sep 17 00:00:00 2001 From: Joshua Fox <4542591+JoshuaFox@users.noreply.github.com> Date: Tue, 26 Nov 2019 17:31:30 +0200 Subject: [PATCH 009/120] version to 1.10 and 5.4.6-1183 --- Makefile | 4 ++-- README.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 21beb16..9a89fcc 100644 --- a/Makefile +++ b/Makefile @@ -56,8 +56,8 @@ app/build:: .build/redislabs/deployer \ .build/redislabs/redislabs: .build/var/REGISTRY \ .build/var/TAG \ | .build/redislabs - docker pull redislabs/operator:5.4.6-1186 - docker tag redislabs/operator:5.4.6-1186 \ + docker pull redislabs/operator:5.4.6-1183 + docker tag redislabs/operator:5.4.6-1183 \ "$(REGISTRY)/redislabs:$(TAG)" docker push "$(REGISTRY)/redislabs:$(TAG)" @touch "$@" diff --git a/README.md b/README.md index 1a3d97c..f027bc9 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,7 @@ export NODE_MEM=1 Configure the container images: ```shell -export IMAGE_REDIS=redislabs/operator:5.4.6-1186 +export IMAGE_REDIS=redislabs/operator:5.4.6-1183 export IMAGE_UBBAGENT=gcr.io/proven-reality-226706/redislabs/ubbagent:1.9 ``` From 0c292c8db12266f9e287075c13c220aae05d2ba7 Mon Sep 17 00:00:00 2001 From: Joshua Fox <4542591+JoshuaFox@users.noreply.github.com> Date: Thu, 28 Nov 2019 15:36:24 +0200 Subject: [PATCH 010/120] adding config for Google Cloud Build --- cloudbuild.yaml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 cloudbuild.yaml diff --git a/cloudbuild.yaml b/cloudbuild.yaml new file mode 100644 index 0000000..d7117e1 --- /dev/null +++ b/cloudbuild.yaml @@ -0,0 +1,20 @@ +steps: +- name: 'gcr.io/cloud-builders/docker' + args: ['build', '--build-arg', 'REGISTRY=gcr.io/proven-reality-226706/redislabs', '--build-arg', 'TAG="1.10"' , '--build-arg', 'MARKETPLACE_TOOLS_TAG="0.7.9"', '--tag', 'gcr.io/proven-reality-226706/redislabs/deployer:1.10', '-f', 'deployer/Dockerfile', '.'] + +- name: 'gcr.io/cloud-builders/docker' + args: ['push', 'gcr.io/proven-reality-226706/redislabs/deployer:1.10'] + +- name: 'gcr.io/cloud-builders/docker' + args: ['pull', 'redislabs/operator:5.4.6-1183'] + +# point from source to target +- name: 'gcr.io/cloud-builders/docker' + args: ['tag', 'redislabs/operator:5.4.6-1183', 'gcr.io/proven-reality-226706/redislabs/deployer:1.10'] + +# my image is pushed to Container Registry +images: [ 'gcr.io/proven-reality-226706/redislabs:1.10'] + + +options: + substitution_option: 'ALLOW_LOOSE' \ No newline at end of file From 3c5f3210e7efa4d7c3e3e8988f23fbcc4ec11b7f Mon Sep 17 00:00:00 2001 From: Joshua Fox <4542591+JoshuaFox@users.noreply.github.com> Date: Thu, 28 Nov 2019 23:17:03 +0200 Subject: [PATCH 011/120] fix docker-tagging in next-to-last step --- cloudbuild.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cloudbuild.yaml b/cloudbuild.yaml index d7117e1..93902f1 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -10,11 +10,11 @@ steps: # point from source to target - name: 'gcr.io/cloud-builders/docker' - args: ['tag', 'redislabs/operator:5.4.6-1183', 'gcr.io/proven-reality-226706/redislabs/deployer:1.10'] + args: ['tag', 'redislabs/operator:5.4.6-1183', 'gcr.io/proven-reality-226706/redislabs:1.10'] # my image is pushed to Container Registry images: [ 'gcr.io/proven-reality-226706/redislabs:1.10'] options: - substitution_option: 'ALLOW_LOOSE' \ No newline at end of file + substitution_option: 'ALLOW_LOOSE' From 195c5a965f0736d1ac09cc975ccc2cf7ed3c6c21 Mon Sep 17 00:00:00 2001 From: Joshua Fox <4542591+JoshuaFox@users.noreply.github.com> Date: Thu, 28 Nov 2019 23:35:20 +0200 Subject: [PATCH 012/120] dynamic version tag --- cloudbuild.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cloudbuild.yaml b/cloudbuild.yaml index 93902f1..0e2e29c 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -1,19 +1,19 @@ steps: - name: 'gcr.io/cloud-builders/docker' - args: ['build', '--build-arg', 'REGISTRY=gcr.io/proven-reality-226706/redislabs', '--build-arg', 'TAG="1.10"' , '--build-arg', 'MARKETPLACE_TOOLS_TAG="0.7.9"', '--tag', 'gcr.io/proven-reality-226706/redislabs/deployer:1.10', '-f', 'deployer/Dockerfile', '.'] + args: ['build', '--build-arg', 'REGISTRY=gcr.io/proven-reality-226706/redislabs', '--build-arg', 'TAG="${TAG_NAME}"' , '--build-arg', 'MARKETPLACE_TOOLS_TAG="0.7.9"', '--tag', 'gcr.io/proven-reality-226706/redislabs/deployer:${TAG_NAME}', '-f', 'deployer/Dockerfile', '.'] - name: 'gcr.io/cloud-builders/docker' - args: ['push', 'gcr.io/proven-reality-226706/redislabs/deployer:1.10'] + args: ['push', 'gcr.io/proven-reality-226706/redislabs/deployer:${TAG_NAME}'] - name: 'gcr.io/cloud-builders/docker' args: ['pull', 'redislabs/operator:5.4.6-1183'] # point from source to target - name: 'gcr.io/cloud-builders/docker' - args: ['tag', 'redislabs/operator:5.4.6-1183', 'gcr.io/proven-reality-226706/redislabs:1.10'] + args: ['tag', 'redislabs/operator:5.4.6-1183', 'gcr.io/proven-reality-226706/redislabs:${TAG_NAME}'] # my image is pushed to Container Registry -images: [ 'gcr.io/proven-reality-226706/redislabs:1.10'] +images: [ 'gcr.io/proven-reality-226706/redislabs:${TAG_NAME}'] options: From 25af882c8713fcb12f950587c54cc44a697ed2a8 Mon Sep 17 00:00:00 2001 From: Joshua Fox <4542591+JoshuaFox@users.noreply.github.com> Date: Sun, 1 Dec 2019 09:26:49 +0200 Subject: [PATCH 013/120] cloudbuild that can take a variable --- cloudbuild.yaml | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/cloudbuild.yaml b/cloudbuild.yaml index 0e2e29c..a3b1151 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -1,20 +1,22 @@ steps: -- name: 'gcr.io/cloud-builders/docker' - args: ['build', '--build-arg', 'REGISTRY=gcr.io/proven-reality-226706/redislabs', '--build-arg', 'TAG="${TAG_NAME}"' , '--build-arg', 'MARKETPLACE_TOOLS_TAG="0.7.9"', '--tag', 'gcr.io/proven-reality-226706/redislabs/deployer:${TAG_NAME}', '-f', 'deployer/Dockerfile', '.'] +- name: gcr.io/cloud-builders/docker + id: docker-build-push + entrypoint: bash + args: + - '-c' + - | + export maxver=`docker image ls gcr.io/proven-reality-226706/redislabs/deployer | tr -s " "|cut -d" " -f 2 |tail -n +2 | grep -o "1\.[0-9][0-9]*" |cut -d"." -f2|sort -n -r |head -n 1 ` + export newver=`echo "1.$(($maxver +1 ))"` + echo $newver 'from' $maxver + docker build --build-arg REGISTRY=gcr.io/${PROJECT_ID}/redislabs --build-arg TAG=${newver} --build-arg MARKETPLACE_TOOLS_TAG="0.7.9" --tag gcr.io/${PROJECT_ID}/redislabs/deployer:${newver} -f deployer/Dockerfile . + docker push gcr.io/${PROJECT_ID}/redislabs/deployer:${newver} + docker pull redislabs/operator:${_OP_VERSION} + docker tag redislabs/operator:${_OP_VERSION} gcr.io/${PROJECT_ID}/redislabs:${newver} + docker push gcr.io/${PROJECT_ID}/redislabs:${newver} -- name: 'gcr.io/cloud-builders/docker' - args: ['push', 'gcr.io/proven-reality-226706/redislabs/deployer:${TAG_NAME}'] - -- name: 'gcr.io/cloud-builders/docker' - args: ['pull', 'redislabs/operator:5.4.6-1183'] - -# point from source to target -- name: 'gcr.io/cloud-builders/docker' - args: ['tag', 'redislabs/operator:5.4.6-1183', 'gcr.io/proven-reality-226706/redislabs:${TAG_NAME}'] - -# my image is pushed to Container Registry -images: [ 'gcr.io/proven-reality-226706/redislabs:${TAG_NAME}'] +substitutions: + _OP_VERSION: "5.4.6-1183" options: substitution_option: 'ALLOW_LOOSE' From df421c0ecc1aacb37a71eb5133ceb8a494871256 Mon Sep 17 00:00:00 2001 From: Joshua Fox <4542591+JoshuaFox@users.noreply.github.com> Date: Sun, 1 Dec 2019 10:42:06 +0200 Subject: [PATCH 014/120] take operator version from tag name --- cloudbuild.yaml | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/cloudbuild.yaml b/cloudbuild.yaml index a3b1151..1be5979 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -5,18 +5,14 @@ steps: args: - '-c' - | - export maxver=`docker image ls gcr.io/proven-reality-226706/redislabs/deployer | tr -s " "|cut -d" " -f 2 |tail -n +2 | grep -o "1\.[0-9][0-9]*" |cut -d"." -f2|sort -n -r |head -n 1 ` + export maxver=`docker image ls gcr.io/${PROJECT_ID}/redislabs/deployer | tr -s " " | cut -d" " -f 2 | tail -n +2 | grep -o "1\.[0-9][0-9]*" | cut -d"." -f2 | sort -n -r | head -n 1` export newver=`echo "1.$(($maxver +1 ))"` - echo $newver 'from' $maxver + echo 'new version' $newver 'from' $maxver docker build --build-arg REGISTRY=gcr.io/${PROJECT_ID}/redislabs --build-arg TAG=${newver} --build-arg MARKETPLACE_TOOLS_TAG="0.7.9" --tag gcr.io/${PROJECT_ID}/redislabs/deployer:${newver} -f deployer/Dockerfile . docker push gcr.io/${PROJECT_ID}/redislabs/deployer:${newver} - docker pull redislabs/operator:${_OP_VERSION} - docker tag redislabs/operator:${_OP_VERSION} gcr.io/${PROJECT_ID}/redislabs:${newver} + docker pull redislabs/operator:${TAG_NAME} + docker tag redislabs/operator:${TAG_NAME} gcr.io/${PROJECT_ID}/redislabs:${newver} docker push gcr.io/${PROJECT_ID}/redislabs:${newver} - -substitutions: - _OP_VERSION: "5.4.6-1183" - options: substitution_option: 'ALLOW_LOOSE' From 2619684a3e3b11afb7dfc220bd8438b6723427f5 Mon Sep 17 00:00:00 2001 From: Joshua Fox <4542591+JoshuaFox@users.noreply.github.com> Date: Sun, 1 Dec 2019 11:12:06 +0200 Subject: [PATCH 015/120] work with tags --- cloudbuild.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/cloudbuild.yaml b/cloudbuild.yaml index 1be5979..fd2ed04 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -5,6 +5,7 @@ steps: args: - '-c' - | + docker image ls gcr.io/${PROJECT_ID}/redislabs/deployer export maxver=`docker image ls gcr.io/${PROJECT_ID}/redislabs/deployer | tr -s " " | cut -d" " -f 2 | tail -n +2 | grep -o "1\.[0-9][0-9]*" | cut -d"." -f2 | sort -n -r | head -n 1` export newver=`echo "1.$(($maxver +1 ))"` echo 'new version' $newver 'from' $maxver From 277d15a74e96bca9bb246fa763ef3f3e86913f15 Mon Sep 17 00:00:00 2001 From: Joshua Fox <4542591+JoshuaFox@users.noreply.github.com> Date: Sun, 1 Dec 2019 11:23:35 +0200 Subject: [PATCH 016/120] cloudbuild.yaml --- cloudbuild.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloudbuild.yaml b/cloudbuild.yaml index fd2ed04..80f9376 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -5,7 +5,7 @@ steps: args: - '-c' - | - docker image ls gcr.io/${PROJECT_ID}/redislabs/deployer + echo gcr.io/${PROJECT_ID}/redislabs/deployer export maxver=`docker image ls gcr.io/${PROJECT_ID}/redislabs/deployer | tr -s " " | cut -d" " -f 2 | tail -n +2 | grep -o "1\.[0-9][0-9]*" | cut -d"." -f2 | sort -n -r | head -n 1` export newver=`echo "1.$(($maxver +1 ))"` echo 'new version' $newver 'from' $maxver From 5c79cf72a6aef84ac9027bcbd5e4d35d58044d93 Mon Sep 17 00:00:00 2001 From: Joshua Fox <4542591+JoshuaFox@users.noreply.github.com> Date: Sun, 1 Dec 2019 11:28:51 +0200 Subject: [PATCH 017/120] cloudbuild.yaml --- cloudbuild.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cloudbuild.yaml b/cloudbuild.yaml index 80f9376..36afacb 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -5,7 +5,9 @@ steps: args: - '-c' - | + echo 'REPO--------' echo gcr.io/${PROJECT_ID}/redislabs/deployer + echo '-------REPO' export maxver=`docker image ls gcr.io/${PROJECT_ID}/redislabs/deployer | tr -s " " | cut -d" " -f 2 | tail -n +2 | grep -o "1\.[0-9][0-9]*" | cut -d"." -f2 | sort -n -r | head -n 1` export newver=`echo "1.$(($maxver +1 ))"` echo 'new version' $newver 'from' $maxver From d106cf737bffee4bb837fe2f4c5428e3aff00b08 Mon Sep 17 00:00:00 2001 From: Joshua Fox <4542591+JoshuaFox@users.noreply.github.com> Date: Sun, 1 Dec 2019 11:37:22 +0200 Subject: [PATCH 018/120] cloudbuild.yaml --- cloudbuild.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloudbuild.yaml b/cloudbuild.yaml index 36afacb..1be7e7c 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -6,7 +6,7 @@ steps: - '-c' - | echo 'REPO--------' - echo gcr.io/${PROJECT_ID}/redislabs/deployer + docker image ls -a gcr.io/proven-reality-226706/redislabs/deployer echo '-------REPO' export maxver=`docker image ls gcr.io/${PROJECT_ID}/redislabs/deployer | tr -s " " | cut -d" " -f 2 | tail -n +2 | grep -o "1\.[0-9][0-9]*" | cut -d"." -f2 | sort -n -r | head -n 1` export newver=`echo "1.$(($maxver +1 ))"` From 9e2bcc05b99e6242be82cadee02ad721219c6953 Mon Sep 17 00:00:00 2001 From: Joshua Fox <4542591+JoshuaFox@users.noreply.github.com> Date: Sun, 1 Dec 2019 11:38:55 +0200 Subject: [PATCH 019/120] cloudbuild.yaml --- cloudbuild.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloudbuild.yaml b/cloudbuild.yaml index 1be7e7c..6315370 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -5,7 +5,7 @@ steps: args: - '-c' - | - echo 'REPO--------' + echo 'iALLINREPO--------' docker image ls -a gcr.io/proven-reality-226706/redislabs/deployer echo '-------REPO' export maxver=`docker image ls gcr.io/${PROJECT_ID}/redislabs/deployer | tr -s " " | cut -d" " -f 2 | tail -n +2 | grep -o "1\.[0-9][0-9]*" | cut -d"." -f2 | sort -n -r | head -n 1` From ac43a172a3fb352a4df6343db859e459ba180250 Mon Sep 17 00:00:00 2001 From: Joshua Fox <4542591+JoshuaFox@users.noreply.github.com> Date: Sun, 1 Dec 2019 11:48:31 +0200 Subject: [PATCH 020/120] cloudbuild.yaml --- cloudbuild.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloudbuild.yaml b/cloudbuild.yaml index 6315370..a8298e5 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -6,7 +6,7 @@ steps: - '-c' - | echo 'iALLINREPO--------' - docker image ls -a gcr.io/proven-reality-226706/redislabs/deployer + docker image ls -a "gcr.io/${PROJECT_ID}/redislabs/deployer" echo '-------REPO' export maxver=`docker image ls gcr.io/${PROJECT_ID}/redislabs/deployer | tr -s " " | cut -d" " -f 2 | tail -n +2 | grep -o "1\.[0-9][0-9]*" | cut -d"." -f2 | sort -n -r | head -n 1` export newver=`echo "1.$(($maxver +1 ))"` From b3750952e86498cc27f8d4782e7a83e289a44535 Mon Sep 17 00:00:00 2001 From: Joshua Fox <4542591+JoshuaFox@users.noreply.github.com> Date: Sun, 1 Dec 2019 11:58:23 +0200 Subject: [PATCH 021/120] cloudbuild.yaml --- cloudbuild.yaml | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/cloudbuild.yaml b/cloudbuild.yaml index a8298e5..4134556 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -5,16 +5,13 @@ steps: args: - '-c' - | - echo 'iALLINREPO--------' - docker image ls -a "gcr.io/${PROJECT_ID}/redislabs/deployer" - echo '-------REPO' - export maxver=`docker image ls gcr.io/${PROJECT_ID}/redislabs/deployer | tr -s " " | cut -d" " -f 2 | tail -n +2 | grep -o "1\.[0-9][0-9]*" | cut -d"." -f2 | sort -n -r | head -n 1` - export newver=`echo "1.$(($maxver +1 ))"` - echo 'new version' $newver 'from' $maxver + export newver=`echo $TAG_NAME | cut -d" " -f 1` + export opver=`echo $TAG_NAME | cut -d" " -f 2` + echo 'new version' $newver 'from' $opver docker build --build-arg REGISTRY=gcr.io/${PROJECT_ID}/redislabs --build-arg TAG=${newver} --build-arg MARKETPLACE_TOOLS_TAG="0.7.9" --tag gcr.io/${PROJECT_ID}/redislabs/deployer:${newver} -f deployer/Dockerfile . docker push gcr.io/${PROJECT_ID}/redislabs/deployer:${newver} - docker pull redislabs/operator:${TAG_NAME} - docker tag redislabs/operator:${TAG_NAME} gcr.io/${PROJECT_ID}/redislabs:${newver} + docker pull redislabs/operator:$opver + docker tag redislabs/operator:$opver gcr.io/${PROJECT_ID}/redislabs:${newver} docker push gcr.io/${PROJECT_ID}/redislabs:${newver} options: From f1165916848de73c583c4f05fcebda80f2df0810 Mon Sep 17 00:00:00 2001 From: Joshua Fox <4542591+JoshuaFox@users.noreply.github.com> Date: Sun, 1 Dec 2019 14:58:30 +0200 Subject: [PATCH 022/120] cloudbuild.yaml --- cloudbuild.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cloudbuild.yaml b/cloudbuild.yaml index 4134556..a50093c 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -5,8 +5,8 @@ steps: args: - '-c' - | - export newver=`echo $TAG_NAME | cut -d" " -f 1` - export opver=`echo $TAG_NAME | cut -d" " -f 2` + export newver=`echo "$TAG_NAME" | cut -d"<" -f 1` + export opver=`echo "$TAG_NAME"| cut -d"<" -f 2` echo 'new version' $newver 'from' $opver docker build --build-arg REGISTRY=gcr.io/${PROJECT_ID}/redislabs --build-arg TAG=${newver} --build-arg MARKETPLACE_TOOLS_TAG="0.7.9" --tag gcr.io/${PROJECT_ID}/redislabs/deployer:${newver} -f deployer/Dockerfile . docker push gcr.io/${PROJECT_ID}/redislabs/deployer:${newver} From 36a84c2b5a5f5791ad46deb685238eaef0292633 Mon Sep 17 00:00:00 2001 From: Joshua Fox <4542591+JoshuaFox@users.noreply.github.com> Date: Sun, 8 Dec 2019 12:34:38 +0200 Subject: [PATCH 023/120] POD_NAME env --- manifest/operator.yaml.template | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/manifest/operator.yaml.template b/manifest/operator.yaml.template index cddc4ac..8d26128 100644 --- a/manifest/operator.yaml.template +++ b/manifest/operator.yaml.template @@ -27,3 +27,7 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name From 1843824a7d406fd8ba2821f49b5a297fc0a601c6 Mon Sep 17 00:00:00 2001 From: Joshua Fox <4542591+JoshuaFox@users.noreply.github.com> Date: Thu, 12 Dec 2019 11:58:00 +0200 Subject: [PATCH 024/120] V5.4.6-1186 of Operator; V1.11 of MP package; config uiService;typos --- Makefile | 8 ++++---- deployer/deploy.sh | 8 ++++++++ deployer/deploy_with_tests.sh | 10 +++++++++- manifest/application.yaml.template | 6 +++--- manifest/redis-enterprise-cluster.yaml.template | 2 +- schema.yaml | 6 ++++++ 6 files changed, 31 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index 9a89fcc..d517f3d 100644 --- a/Makefile +++ b/Makefile @@ -3,8 +3,8 @@ include helper/crd.Makefile include helper/gcloud.Makefile include helper/var.Makefile - -TAG ?= 1.10 +OP_VERSION=5.4.6-1186 +TAG ?= 1.11.0 REGISTRY ?= gcr.io/proven-reality-226706 METRICS_EXPORTER_TAG ?= v0.7.1 @@ -56,8 +56,8 @@ app/build:: .build/redislabs/deployer \ .build/redislabs/redislabs: .build/var/REGISTRY \ .build/var/TAG \ | .build/redislabs - docker pull redislabs/operator:5.4.6-1183 - docker tag redislabs/operator:5.4.6-1183 \ + docker pull redislabs/operator:${OP_VERSION} + docker tag redislabs/operator:${OP_VERSION} \ "$(REGISTRY)/redislabs:$(TAG)" docker push "$(REGISTRY)/redislabs:$(TAG)" @touch "$@" diff --git a/deployer/deploy.sh b/deployer/deploy.sh index ec2f7c9..94af808 100755 --- a/deployer/deploy.sh +++ b/deployer/deploy.sh @@ -87,6 +87,14 @@ export SERVICE_ACCOUNT="$(/bin/print_config.py \ --values_mode raw)" echo "Admin Service Account = $SERVICE_ACCOUNT" + + +shopt -s nocasematch +case "$INGRESS_AVAILABLE" in + "true" ) export UI_SERVICE=LoadBalancer ;; + *) export UI_SERVICE=LoadBalancer ;; +esac + # Put CRD in configmap so elvated Job can install it kubectl create configmap crd-cm --from-file=crd=/bin/crd.yaml # Create elavated job to create Job diff --git a/deployer/deploy_with_tests.sh b/deployer/deploy_with_tests.sh index b1051e1..ee238e1 100755 --- a/deployer/deploy_with_tests.sh +++ b/deployer/deploy_with_tests.sh @@ -97,6 +97,14 @@ export SERVICE_ACCOUNT="$(/bin/print_config.py \ --values_mode raw)" echo "Admin Service Account = $SERVICE_ACCOUNT" + + +shopt -s nocasematch +case "$INGRESS_AVAILABLE" in + "true" ) export UI_SERVICE=LoadBalancer ;; + *) export UI_SERVICE=LoadBalancer ;; +esac + # Put CRD in configmap so elvated Job can install it kubectl create configmap crd-cm --from-file=crd=/bin/crd.yaml # Create elavated job to create Job @@ -136,4 +144,4 @@ fi clean_iam_resources.sh -trap - EXIT \ No newline at end of file +trap - EXIT diff --git a/manifest/application.yaml.template b/manifest/application.yaml.template index c88ef36..48070a8 100644 --- a/manifest/application.yaml.template +++ b/manifest/application.yaml.template @@ -6,13 +6,13 @@ metadata: annotations: kubernetes-engine.cloud.google.com/icon: >- data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIIAAACCCAYAAACKAxD9AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3wIFEy8OiWavSwAAIABJREFUeAEAN4DIfwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA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BQMAEAQFAPX9/QDz/PwA8/z8APf+/gD8//8AAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAABAQD6//8A7Pr6APD7+wALBAMADQQEAA4DBAAIAwIABAICAAEAAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAP8AAAD7/v4A9f39APH7/ADy/PwA9/7+AAAAAAACAQAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAAAAMBAQAIAgIADQMDAA4EBAALBAQABQIBAP8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAPz+/wD5/v0A8fz8APD7+wD1/f4A/wAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAQIA+//+AO37+wD3/v4AEAQEAA8FBAAIAwMABAICAP///wD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAEAQEADgIEAAwFBAAS/f0A9fz9APP8/AD0/f0A+f7+AP4AAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAQEA/QAAAPL8/QD0/P0A7gL7AA4EBAAOBAQACgQDAAYCAgABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEAAP3+/wD5/v8A9f39APL7/ADz/fwA+f7+AP8AAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAAD/AAAAAv//AAUDAwAKAgIADgQEAAwEAwAJAwMABAEBAAAAAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAPz//gD2/P4A8vz8APP8/AD2/v0A/f8AAAEAAAABAAAA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAPX9/QD4/f0A8fz8AAwDAwAPBQQACgMEAAUCAQAAAAAA/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAAAAAAAAAgAAAAkDAwAIAwMADgUEAPf9/QDz/P0A9Pz8APb+/gD8/v4AAAAAAAIBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAP8AAAD3/v4A+fr6APL8/AAJAwMADAMDAAoDAgAGAgIAAwICAAIAAAAAAAAA/v//APv//wD6/f4A9f79APX8/QD3/v4A+/7+AP8AAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAAD+AAAAAwAAAAkCAgAMAwMADQUFAAoDAgAHAgIAAgEBAAAAAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAA/wAAAPr+/gD2/fwA8fuwfuByAAAgAElEQVT8APT9/gD4/f0A/gAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADJfzaABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAPP9/QD3/f0A8vz8AAsEAwAOBAQACwQEAAcBAQABAQEA/wAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAYCAgAGAgIADAQEAPf9BAD2/P0A8/38APX8/QD5//8A/gAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAP8AAAD3/v4A+P7+APP7/AAGAwEABQICAAQAAQADAQEA/wAAAP3//wD8//8A+v7+APv//gD8/wAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/gAAAAD//wAEAQEADAMDAA0EAwAOBAQACAMDAAUCAgABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAEAAAD/AAAA9/7+APT8/ADz/PwA9Pz9APr+/gD9AAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAP0AAAD0/v4A9vn9APL8/QAKA/wADgQEAAwEAwAIAgIAAgEBAAAAAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAQEADQQEAAsEBAAP/QQA9vz+APL9/AD1/fwA+P3+APz//wAAAQEAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAA+wD/APv9/wAA/P4AAQD/AAEBAQD/AAAAAP//AP4BAQAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAP8AAAAGAQEACgQDAA4EBQAMBAMACQMDAAMBAQABAAAA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAD9AAAA+f39APT8/ADy/P0A9f39APr+/gD/AAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAQEA9/7+APj9/QD0/fwACvsDAA0DBAAMBQMACAIDAAQCAQABAAAA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAAAAAAAAAQAAAAgDAwAIAwMADf8DAPf8BQDy/PwA8/39APn9/QD7//4A/gABAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAABAAAAAAEA/wAAAAAAAAAAAQEAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAEAAAAIAgIACgMCAA4EBQALAwMACAMCAAMBAQD/AAAAAAAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAP8AAAD7//8A9/z9APX9/ADy/P0A9v3+APv//gD/AAAAAgAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAEBAPn+/wD5/fwA8vz8AAr8AwANAwMADQUEAAgCAgAGAgIAAgEBAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAAAAAAGAQIABgUCAAwEAwD3/f4A9Pz7APP8/QD2/v4A+v7+AP4AAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAAAAIBAQAJAgEACwMDAA4EBQAMBAMABwIDAAEA/wD/AAAA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAP4AAAD6/v4A9/39APP8/QDy/PwA9/79APz/AAABAAAAAgAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAABAAD+AAAA8vz8APX9/QDw/PwACwMDAA0EAwAKBAQACAICAAIBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP4AAAAAAAAAAgEBAAwEAwAKBAMAEP7+APX8/ADz/P0A9f39APj+/gD8//8AAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAAAAQBAQAJAgIADAQEAA4EAwALAwMABAICAAAAAAD/AP8A/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP7//wD6/v4A9f39APP8/QD0/PwA+P7+AP3/AAACAQAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAADAQEAAAAAAPb9/gD3/f4A8fz8AAkD+wANBAQADAMDAAoDAwAEAgIAAQAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAA/wAAAAAAAAAHAgIABwYGAA4F/wD4/f0A8/z9APP8/AD2/f0A+///AP8AAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAAAAYBAQALAwMADgQEAAwEAwAHAgMABAIBAAAAAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAPz//wD3/f0A9fz8APL8/QD1/f4A/P/+AP4AAAACAAAAAwEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQABAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgEAAAAAAAD7//8A+/z9APT9/QAHAQIACwMDAA4EBAANBQMABgIDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAAAAAAAAAwEBAAwEBAAJAwQAEQUEAPb9/gDy/PsA9P39APf9/gD/AAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD+AAAAAQAAAAcCAgANAwMAEAUEAAoDAwAFAgIAAwEBAP8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPv+/gD0/f0A8vv8APT8/QD4//4A/P//AAEAAAABAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD////kAP79ogAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL8/fMABALfAAAAM/8BAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAA//8AAPj//gD5/v4A8fz8AAn7AgAOBAQADQMEAAsEAwACAQEA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAIBAAAHAwMABgMDAAwEAwD6/v4A8vz9APL8+wD1/P0A+/8AAAEBAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAAD/AAAAAAAAAAoCAgAQBQQADgQFAAkDAgAEAgIAAQAAAAAAAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAPj9/QDz/P0A8fv7APX+/gD8/v4A/gAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//8A/wD/zv/9/pj+//2sAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQA8////Zn/AAMPAQAAQQABAQQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAAD7//8A/Pz9APb9/gDuAPoACwUFAA8EAwAMBAQABgICAAAAAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAABQICAAUCAgAIAwMAEQUEAPT8/ADy/P0A8/z8APn//wD/AAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD+//8AAQAAAAoDAwARBQUADwUEAAkCAgACAQIAAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAA/gAAAPb8/ADx/P0A8fv7APj+/gD8//8A/wAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAAAAAAD/AP7/4/3//a77/vul+Pr+1m3x+vYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmA8HBgsFBi8AAgFc/wIBTQABAR4BAQEDAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAAAAEAAAAAAQEABQABAAsDAgAOBAQADQUEAAgCAwACAQAAAAAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAA/f//APv+/gD3/f4A8fz8APL8/AD5/f0A/gEBAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAf//AAkDAwAPBAQAEAUFAAgDAgAEAgIAAQAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAA+v/+APX8/QDy+/wA8/38APj+/wD+//8A/wAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAAD///8ABgICAA8EBP8NAwPcCgIBoQwA/qYUAf/iEtvp/QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJ8RChkQCQdYAgICaPn//yb5/v4AAAEBAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAAD/AAAAAgAAAAQBAQAIAgIADQUEAA0CAwAJBAQABQEBAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAP8A/wD7/v8A9v39APL7/ADz/f0A+P39AP3/AAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAABAEBAAkCAgAMBAQADQQEAAoDAgAFAgIAAAAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//wAA+v7+APX9/ADy/PwA9Pz9APn//gD+/gAAAAEAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAAAAAABAAAACAECAAsEAwAPBQUACwQCAAYBAvcCAAHn////0v///rH9+/myLN7q7gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM8dFAMNCwkZAAEBOvv/ATP6/v4Q9f7+APL7+wD0/f0A+f7+AP4AAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAEAAAAAAAAA+///APr9/QD2/f0A8AICAAsDAwANBAQACwQDAAYCAgACAQEAAAAAAAEAAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAQABAAYCAQAFAgIACwMEABH+/QD1/PwA9v7+APn+/wD9/v4A/gAAAAAAAAADAAAABAICAAcCAgAKAwIACwMEAAcDAgAEAQEAAAAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAA/wAAAAD/AAD9AP8A+f39APT8/QDy/PwA9v7+APn+/gD/AAAAAAAAAAIBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAAADAAAACAICAAsEAwAPBQQACgMEAAYCAgACAAAA/wAACf8AACIAAAEiAQECMgQGBxgAAACIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADXISEBAwX7NP4CAmEAAQFIAQEBHAAAAAX/AAAA+v7+APf8/QDy/PwA9P38APj+/wD9//8A/wAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAAD///8AAgEBAAYBAQAKAwMADQUEAAsDAwAIAgMABAEAAAABAQABAAAA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAEAAP3+/wD8//8A/f7/APz//wD+AP8AAQAAAAIAAQAEAgEABAEBAAQCAgABAAAAAAAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAA/wAAAAAA/wD8/v8A9/3+APP8/ADz/fwA9/3+APz//wD+/wAAAQEAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAAAAAAAAAFAQEACAICAA0EBAANBAQACQMCAAUCAgABAAAAAAAAAP8AAAAAAAAAAAAAAAEAAAAAAAAA/f7+1yrY4ioAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkI9A35/wlmAAEBWgABAQr/AAAAAAAAAAEAAAAHAgIABgYCAAsFBAD5/f4A8/z7APP8/QD2/f0A+///AP8AAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAEBAQD/AAAA+f7+APr//wDz/f0ABwIBAA0DBAANBAQACgQDAAYBAQAAAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAAAA/wAAAgEBAAcBAQAAAgAAAAAAAP8AAAABAAAAAAAAAAAAAAAAAAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAP8AAAD7//4A9fv9APL9+wDz/P0A+P3+AP4AAAAAAAAAAQAAAAIBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAAAAAAFAQEACwQDAA4EBAANAwQACAMCAAMBAQAAAAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP7/AADy/PwPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8PkFDQUGAC0DAQEKAAAAAAAAAAAAAAAA/wAAAAAAAAAAAAAABAEBAAUEAwAKAwIAEgX+APT8/ADy/f0A8/v8APn//gD/AAEAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAQEAAAAAAP3//wD1/f8A9v36AO77+wAMAwMADwUEAA0EBAAGAgIAAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAAAAAAAAAAAAAAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD5/v4A8/z8APD7+wD0/P0A+///AP8AAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/gAAAAEAAAAHAgIADQMDABAFBAALBAQABQIBAAEAAQAAAAAA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8/v4A8Pv8AOf5+f4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADS8PIBHg8MARIGBQAHAwMA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAEBAQAGAwMAEgMDAA7//wD4B/4A8fr7APH7+wD3/v4A/gAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAA+///AO/8/ADx/PwACAMCABAEBQAPBQQACQMDAAIBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAD5//0A8fzzijQAACAASURBVPv8AO/6+wD1/f0A/gAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKAgIAEAUEABAFBQAJAwMAAgEBAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9vz9AO36+gDu+/sA+v7+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPoBAADm+PkA+P/+ABUGBgAKAwMAAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAEBAAwDAwAKCQMAEwYGAPL8+wDw+/wA9fz8APwAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP4AAAD2/f4A9v35AOz6+wAPBQMAEAQFAAsEBAAEAgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//AAD5/v0A8Pr7APD8/AD1/PwA/wEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAMBAQAMAwMADwQDAA8FBQAHAwMAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+P39APD7+wDt+vsA9/7+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgABAP0A/wDs+vsA9v39ABEFBQAQBQUACAICAAIBAQD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAIAwIACAIGABEFBAD1/f0A8fv9APP8/AD5/v4A/wAAAP8AAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+f/+AOv6/gDv+/wADAMDAA8EBAANBQQABgIDAAIBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAA/wAAAP///wD4/v4A8Pv7APD7/AD2/f0A/gAAAAEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAAABAAAAAAAAAAYBAQAMBAQAEAQDAAsEBQAGAgIAAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8A9v7+APL7+wDw+/wA9v7+AP7//wABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD1/f4A+P75APL7/AANBAQADgUEAAgCAwAEAgEAAQAAAAAAAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEABgEDAAUCAwAMA/4A+P4FAPP8/ADz/P0A9v39APz//wD//wAAAAEAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAPv//wDw/PwA8/39AAv8+wAOBAUADQQDAAcDAgAEAAIAAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/f//AP3//wD4/f4A8/38APL7/AD3/v4A/P//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAAAAAABAAAAAwAAAAgDAgANAwQADgQEAAoEAwAEAQEAAQEBAAEAAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAP8AAAD8/v8A9v39APL8/ADy+/sA+P//AP7//wD/AAAAAAAAAAEAAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAD9/wAA8v39APb9/QDx/PwADAMDAA0FBQAKAwIABQEBAAIBAQAAAAAA/wAAAAAAAAAAAAAAAAAAAP8AAAAAAAAAAQAAAAUCAgAFAgIACgMDABAF/gD1/fwA8/v9APX9/QD6/v4A/QAAAP8AAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAP8AAAD8//8A8vz9APX9/gDw+wIACgQDAAoCAwAHAwIABAEBAAEAAQAAAAAA/v/+APsA/wD4/f4A9vz9APb9/QD5//4A/f8AAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAP///wACAQEABQEBAAoEAwANAwMACwQEAAoCAgADAgIAAQAAAP8AAAAAAAAA/wAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAP7//wD6/v8A9f39APP8+wD0/f0A+P7/AP7//wAAAAAAAAAAAAEAAAAAAAAA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAA/AAAAPT9/QD2/v4A8Pz8AAwEAwANAwQADAQDAAYBAgADAgEAAAAAAAAAAAD/AAAAAAAAAAAAAAD/AAAAAAAAAAAAAAADAQEACgMDAAgCAgAPBAAA9f38APT8/QD0/fwA9/3+APz//wD/AAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAD/AAAA/P//APP9/QD1/f4AAwH7AAUBAQADAQEAAgEBAAAAAAD9//8A/P//APv+/gD+AAAA/wAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAP8AAAADAAAABgMCAAsDBAANBAQADAQDAAcCAgABAQEAAgAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAA/wAAAP3//wD5/v4A9Pz9APL8/AD2/PwA+v//AP4AAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAP7//wD3//8A+Pr+APEA+wAKBAQADgMEAA0FBAAIAwEAAwECAAEAAAD/AAAAAAAAAAAAAAAAAAAAAAAAAP8AAAAAAAAAAQEBAAUBAAAQAgUADgQEAPf9BQD1/PwA8vz8APX9/QD7AAAA////AAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAP4A/wD+/wAA+///AAAAAAABAAAAAAAAAP8AAAAAAAAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAEAAAADAAAABwICAA0EAwAOBQUACwMDAAUCAgABAAAA/wAAAAAAAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPz//gD3/f8A9Pz8APL8/AD1/f0A/P7+AAABAQABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAEAAAAAAAAA+wAAAPD8/ADy/PwACvwDAA0EAwAPBAQACQMDAAQBAgABAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAQEACwQDAAsDAgASBQUA8/39APD8+wD0/P0A+v//AP7//wD/AAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAEAAAAEAQAACAIDAA8EAwAOBQUACgMDAAIBAQAAAAAAAAAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//APv//wD4/v0A8fv8APL8/QD3/f0A/QAAAAEAAAABAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAQAAAAAAAAD1/f0A9f39AO36+wAOBAQAEAQFAAsEAgAEAgIAAgEBAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAAQDAQASAgEADwQDAPf+/gDx+/wA8vz8APb+/gD+//8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAEAAAACgQDABAEBQAPBgUACAEBAAABAQD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/v//APz//wD1/f0A8vv7APH8/QD4/v4AAAAAAAIAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAA+///AOz7+wDw/PwACwICAA8FBAANBAQABwICAAMBAQABAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8BAQACAQEADAICAAsEBAAT/v4A8/v7APH7/AD1/f0A+///AP4AAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAIAAAAFAQEADAQEABAFBAANBAQABQEBAAABAQD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/f7+APr//wDz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AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//////wAAP/rAAAA0gD+/8EA/f/BAPz72F3w+O8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKQTDB3/BAItAAABPwABATgAAQAoAQAAEgABAQQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD+AAAAAAAAAAMBAAAEAAEABgICAAMCAQACAAAA/wAAAP/+AAD7AP4A+/7/APv//wD///8AAAEBAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAA//cA/v/j////zAH/AMD9AP/J8/T422z2+/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgEAkRAgUFIwABADgBAgE/AAEBMwAAARsBAQAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAP8AAAAA//8AAAEBAAD/AAAAAQAAAP//AAAAAAABAQEAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////X/AP/XAv//xQD/AL3//v7S/uv0418AAP4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAng0EBwMJCRcBAAIuAQAARQADAT0AAAAqAQEBBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//+8A///N/f7+vQL+/73+AgLdX+ry7gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMMBwIACAcL/wD8HwADBEQBAQFIAAEBNwEBARAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//AAAA/+n+AADBAv3/s//9/cX8/v7oYe/39wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAChCwAB/QkMBgP9/hMBBQI3AQEDSwABAEQBAQAfAAABAAABAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//8A/wAAAAD//9sA//+4AP7/tPsA/sz4/ALzau/z+wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHgQBwIpBAQNAQICKwABAUgBAQFFAAAAMAEBAQYBAQEC/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////AAAA+QD/ANL///+3/wD8uP37+dQD/g/2XvDt/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAohcKCwD/ASABAAI+AAICRQEBATYAAAAUAAEBBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD7AP//8f///84AAP64Af0AvP79/d9e7vb0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJ4OCAgDBgQaAQMCMwEAAUABAQA7/wEBHwEBAQ4AAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD5AP7/6wAA/8j///+7/v3+xPj9/eBn7/f2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAADXjuTZAAAgAElEQVQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACcDAYECAkHE/4AACsBAgE9AQEBPgABASwAAQETAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD7AP//5gAAAMT//f66//79xfv+BOVj7vH4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAjQAAARYVCg8A/gIf/wQCPgECAUMBAAE0AAEBFgAA/wUAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAA/wD4AP8A6P8A/8gA//+1AAAAwvv8++li7ff5AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAChFQwI/wIAFgP/Aj8BAgFI/wEANQAAARcAAAAJAQABBAABAAEAAAAA/////gAAAPsAAAD1Af//5AAAAMf///66Af7/yP3+/+tf7fX7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnxgQAgf49wv9BwUr/wADNwIAADL/AAEeAQH/Cv4BAP8B/wDv////3QAAAMz/+vrLBwH/3foHB/pe5/L+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsQAAAvUSAgoA/gQE+/0AAAL0A/ld//f3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACObUCAAAAinSURBVAAAAAAAAAAAAAAAAAGSCG33AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJ6rTUEjcY3eAAAAAElFTkSuQmCC - marketplace.cloud.google.com/deploy-info: '{"partner_id": "redislabs-public", "product_id": "redis-enterprise", "parnter_name": "Redis Labs"}' + marketplace.cloud.google.com/deploy-info: '{"partner_id": "redislabs-public", "product_id": "redis-enterprise", "partner_name": "Redis Labs"}' labels: app.kubernetes.io/name: "$APP_INSTANCE_NAME" spec: descriptor: type: Redis Enterprise Operator - version: 1.9.0 + version: $TAG description: |- Redis Operator makes it easy to deploy and manage Redis Enterprise on Kubernetes. maintainers: @@ -22,7 +22,7 @@ spec: - description: 'User Guide: Redis Enterprise' url: https://support.redislabs.com notes: |- - See more details and manual installation instrcutions here https://github.com/RedisLabs/gkemarketplace + See more details and manual installation instructions here https://github.com/RedisLabs/gkemarketplace matchLabels: app.kubernetes.io/name: "$APP_INSTANCE_NAME" componentKinds: diff --git a/manifest/redis-enterprise-cluster.yaml.template b/manifest/redis-enterprise-cluster.yaml.template index 6d78c74..2c20c61 100644 --- a/manifest/redis-enterprise-cluster.yaml.template +++ b/manifest/redis-enterprise-cluster.yaml.template @@ -10,7 +10,7 @@ spec: persistentSpec: enabled: true storageClassName: "standard" - uiServiceType: LoadBalancer + uiServiceType: ${UI_SERVICE_TYPE} username: $REDIS_ADMIN redisEnterpriseNodeResources: limits: diff --git a/schema.yaml b/schema.yaml index 8fd4ea0..02c3354 100644 --- a/schema.yaml +++ b/schema.yaml @@ -46,6 +46,12 @@ properties: default: $REGISTRY/ubbagent:$TAG x-google-marketplace: type: IMAGE + INGRESS_AVAILABLE: + type: string + title: Ingress Supported + description: Indicates whether the cluster is detected to have Ingress support. + x-google-marketplace: + type: INGRESS_AVAILABLE REPORTING_SECRET: type: string x-google-marketplace: From 37b64785e1f5c9e05d159981efc053cde8b3ff2e Mon Sep 17 00:00:00 2001 From: Joshua Fox <4542591+JoshuaFox@users.noreply.github.com> Date: Thu, 12 Dec 2019 12:40:45 +0200 Subject: [PATCH 025/120] uiService --- deployer/deploy.sh | 2 +- deployer/deploy_with_tests.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/deployer/deploy.sh b/deployer/deploy.sh index 94af808..e2fd22f 100755 --- a/deployer/deploy.sh +++ b/deployer/deploy.sh @@ -92,7 +92,7 @@ echo "Admin Service Account = $SERVICE_ACCOUNT" shopt -s nocasematch case "$INGRESS_AVAILABLE" in "true" ) export UI_SERVICE=LoadBalancer ;; - *) export UI_SERVICE=LoadBalancer ;; + *) export UI_SERVICE=ClusterIP ;; esac # Put CRD in configmap so elvated Job can install it diff --git a/deployer/deploy_with_tests.sh b/deployer/deploy_with_tests.sh index ee238e1..d6bc434 100755 --- a/deployer/deploy_with_tests.sh +++ b/deployer/deploy_with_tests.sh @@ -102,7 +102,7 @@ echo "Admin Service Account = $SERVICE_ACCOUNT" shopt -s nocasematch case "$INGRESS_AVAILABLE" in "true" ) export UI_SERVICE=LoadBalancer ;; - *) export UI_SERVICE=LoadBalancer ;; + *) export UI_SERVICE=ClusterIP ;; esac # Put CRD in configmap so elvated Job can install it From 4cef9fcefa48ffafb8e4429336c6201707c4d451 Mon Sep 17 00:00:00 2001 From: eranchetz Date: Sun, 15 Dec 2019 05:57:04 +0200 Subject: [PATCH 026/120] switch version to 1.11 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index d517f3d..73f17b5 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ include helper/gcloud.Makefile include helper/var.Makefile OP_VERSION=5.4.6-1186 -TAG ?= 1.11.0 +TAG ?= 1.11 REGISTRY ?= gcr.io/proven-reality-226706 METRICS_EXPORTER_TAG ?= v0.7.1 From b3202eafef7711418fff07a92c59d0471d36e6fa Mon Sep 17 00:00:00 2001 From: Joshua Fox <4542591+JoshuaFox@users.noreply.github.com> Date: Sun, 15 Dec 2019 10:02:30 +0200 Subject: [PATCH 027/120] edit Readme significantly --- README.md | 89 +++++++++++++++++++++++++++---------------------------- 1 file changed, 43 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 2386ac4..c3f9d1a 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,6 @@ This bundles [Redis Enterprise](https://www.redislabs.com/) into a form suited t ## Design -![Architecture diagram](resources/image) - ### Solution Information Redis-Enterprise cluster is deployed within a Kubernetes StatefulSet. @@ -17,7 +15,7 @@ The deployment creates two services: - A client-facing one, designed to be used for client connections to the Redis-Enterprise cluster with port forwarding or using a LoadBalancer, -- and service discovery - a headless service for connections between +- Service discovery: a headless service for connections between the Redis-Enterprise nodes. Redis-Enterprise Kubernetes application has the following ports configured: [TODO] @@ -28,10 +26,14 @@ Redis-Enterprise Kubernetes application has the following ports configured: [TOD ## Quick install with Google Cloud Marketplace Get up and running with a few clicks! Install this Redis Enterprise app to a -Google Kubernetes Engine cluster using Google Cloud Marketplace. +Google Kubernetes Engine cluster using Google Cloud Marketplace. You can do this from the Applications tab in the GKE page in the Cloud Console. ## Command line instructions -For testing, you may want to deploy straight from your command line, "simulating" what Marketplace does when it deploys. This is not a perfect simulation as the environment differs. For example, this entire process will not work unless you provide the [secrets](https://kubernetes.io/docs/concepts/configuration/secret/). +For testing, you may want to deploy straight from your command line, partially simulating what Marketplace does when it deploys. + +This is not a perfect simulation. For example, this entire process will not work unless you provide the [secrets](https://kubernetes.io/docs/concepts/configuration/secret/). + +Steps here are idempotent so feel free to just rerun steps in a script if you are working on later steps. ### Prerequisites @@ -78,15 +80,12 @@ git clone https://github.com/RedisLabs/redis-enterprise-k8s-docs.git #### Install the Application resource definition -An Application resource is an addition to the Kubernetes metamodel: A collection of individual Kubernetes components, -such as Services, Deployments, and so on, that you can manage as a group. +An Application resource is an addition to the Kubernetes metamodel: A collection of individual Kubernetes components, such as Services, Deployments, etc, that you can manage as a group. -The Application resource is defined by the -[Kubernetes SIG-apps](https://github.com/kubernetes/community/tree/master/sig-apps) -community. The source code can be found on +The Application resource is defined by the [Kubernetes SIG-apps](https://github.com/kubernetes/community/tree/master/sig-apps) community. The source code can be found on [github.com/kubernetes-sigs/application](https://github.com/kubernetes-sigs/application). -To add Application to the metamodel and thus set up your cluster to understand Application resources, run the following command. It is idempotent and so need to be run only once; subsequent runs do nothing. +To add Application to the metamodel and thus set up your cluster to understand Application resources, run the following command. ```shell kubectl apply -f "https://raw.githubusercontent.com/GoogleCloudPlatform/marketplace-k8s-app-tools/master/crd/app-crd.yaml" @@ -102,8 +101,7 @@ cd gkemarketplace #### Configure the app with environment variables -Choose an instance name and -[namespace](https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/) +Choose an instance name and[namespace](https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/) for the app as follows. In most cases, you can use the `default` namespace. ```shell @@ -121,24 +119,21 @@ Set the username for the app: ```shell export REDIS_ADMIN=admin@acme.com -`` - +``` Set the CPU and Memory for nodes: -```shell +```shell export NODE_CPU=1000 export NODE_MEM=1 ``` -Configure the container images. Note that these version numbers might not be up to date. +Configure the container images. Update version numbers as necessary. ```shell - -export IMAGE_REDIS=gcr.io/proven-reality-226706/redislabs:1.10 - -export IMAGE_UBBAGENT=gcr.io/proven-reality-226706/redislabs/ubbagent:1.9 +export IMAGE_REDIS=gcr.io/proven-reality-226706/redislabs:1.11 +export IMAGE_UBBAGENT=gcr.io/proven-reality-226706/redislabs/ubbagent:1.11 ``` #### Create namespace in your Kubernetes cluster @@ -151,9 +146,8 @@ kubectl create namespace "$NAMESPACE" #### Prerequisites for using Role-Based Access Control -To use [role-based access control](https://kubernetes.io/docs/reference/access-authn-authz/rbac/) -for the app, grant your user the ability to create roles in -Kubernetes by running the following idempotent command: +To use [role-based access control](https://kubernetes.io/docs/reference/access-authn-authz/rbac/) for the app, grant your user the ability to create roles in +Kubernetes: ```shell kubectl create clusterrolebinding cluster-admin-binding \ @@ -161,20 +155,19 @@ kubectl create clusterrolebinding cluster-admin-binding \ --user $(gcloud config get-value account) ``` -For steps to enable role-based access control in Google Kubernetes Engine, see -the [Kubernetes Engine documentation](https://cloud.google.com/kubernetes-engine/docs/how-to/role-based-access-control). +For steps to enable role-based access control in Google Kubernetes Engine, see the [Kubernetes Engine documentation](https://cloud.google.com/kubernetes-engine/docs/how-to/role-based-access-control). #### Expand the manifest template Use `envsubst` to expand the template. We recommend that you save the expanded manifest file for future updates to the application. -1. Expand `RBAC` YAML file. You must configure RBAC related stuff. +1. Expand `RBAC` YAML file. You can configure the RBAC first. ```shell # Define name of service account export SERVICE_ACCOUNT=redis-enterprise-operator - + # Expand rbac.yaml.template envsubst '$APP_INSTANCE_NAME $NAMESPACE $SERVICE_ACCOUNT' < manifest/rbac.yaml.template > "${APP_INSTANCE_NAME}_rbac.yaml" ``` @@ -192,20 +185,15 @@ expanded manifest file for future updates to the application. Use `kubectl` to apply the manifest to your Kubernetes cluster: ```shell -# rbac.yaml kubectl apply -f "${APP_INSTANCE_NAME}_rbac.yaml" --namespace "${NAMESPACE}" - - # crd.yaml: Custom Resource Definition kubectl apply -f deployer/crd.yaml - -# manifest.yaml kubectl apply -f "${APP_INSTANCE_NAME}_manifest.yaml" --namespace "${NAMESPACE}" ``` #### View the app in the Google Cloud Platform Console -Get the Goocle Cloud Console URL for your app, then open this URL in your browser: +Get the Google Cloud Console URL for your app, then open this URL in your browser: ```shell echo "https://console.cloud.google.com/kubernetes/application/${ZONE}/${CLUSTER}/${NAMESPACE}/${APP_INSTANCE_NAME}" @@ -213,18 +201,17 @@ echo "https://console.cloud.google.com/kubernetes/application/${ZONE}/${CLUSTER} #### Get the status of the cluster -By default, the application does not have an external IP address. To get the -status of the cluster, use `kubectl port-forward` to access the dashboard on the master +By default, the application does not have an external IP address. Use `kubectl port-forward` to access the dashboard on the master node at `localhost`. ``` -kubectl port-forward redis-enterprise-cluster-0 8443 +kubectl port-forward redis-enterprise-cluster-0 8443 ``` #### Getting the Admin Password -See [instructions here](https://docs.redislabs.com/latest/rs/faqs/) +See [instructions here](https://docs.redislabs.com/latest/rs/faqs/). #### Access the Redis-Enterprise service externally @@ -232,7 +219,10 @@ See [instructions here](https://docs.redislabs.com/latest/rs/faqs/) kubectl get services -n $NAMESPACE ``` -**NOTE:** It might take some time for the external IP to be provisioned. +**NOTE:** + +1. It might take some time for the external IP to be provisioned. +2. This works out-of-the-box in GKE but not in Anthos, where special measures are needed to configure the Load Balancer. # Uninstall the Application @@ -258,17 +248,14 @@ export NAMESPACE=default ### Delete the resources -> **NOTE:** We recommend to use a kubectl version that is the same as the version of your cluster to avoid unforeseen issues. +**NOTE:** We recommend to use a kubectl version that is the same as the version of your cluster . -To delete the resources, use the expanded manifest file used for the -installation. +To delete the resources, use the expanded manifest file used for the installation. Run `kubectl` on the expanded manifest file: ```shell -# manifest.yaml kubectl delete -f "${APP_INSTANCE_NAME}_manifest.yaml" --namespace "${NAMESPACE}" -# rbac.yaml kubectl delete -f "${APP_INSTANCE_NAME}_rbac.yaml" --namespace "${NAMESPACE}" ``` @@ -286,8 +273,7 @@ By design, removal of StatefulSets in Kubernetes does not remove PersistentVolumeClaims that were attached to their Pods. This prevents your installations from accidentally deleting stateful data. -To remove the PersistentVolumeClaims with their attached persistent disks, run -the following commands: +To remove the PersistentVolumeClaims with their attached persistent disks: ```shell for pv in $(kubectl get pvc --namespace $NAMESPACE \ @@ -308,3 +294,14 @@ kubectl delete persistentvolumeclaims \ gcloud container clusters delete "$CLUSTER" --zone "$ZONE" ``` +# Upgrading the version + +When a few version of the Redis Operator comes out, you will want to upgrade the version. + +1. Upgrade `OP_VERSION=5.4.6-1186` in `Makefile`. +2. Increment `Makefile:TAG ?= 1.11` in `Makefile`. This will increment the version of both this Marketplace package and the UBB image that provides the sidecar. +**Note**: Do not use a patch number like 1.12.0; use only major-minor. +3. `make -B app/build` (where `-B` forces the build even if no change is detected). + * This builds the image and pushes it to gcr.io + +(`cloudbuild.yaml` allows building this in Cloud Buld instead of `make`. It is not yet in active use, pending permissions for triggers and adoption of a process. ) \ No newline at end of file From 1627c6fbf21b21158ce707f570dcacddd23b3d22 Mon Sep 17 00:00:00 2001 From: Eran Chetz Date: Sun, 22 Dec 2019 11:13:59 +0200 Subject: [PATCH 028/120] Fix INGRESS_AVAILABLE type to boolean --- schema.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schema.yaml b/schema.yaml index 02c3354..ea96840 100644 --- a/schema.yaml +++ b/schema.yaml @@ -47,7 +47,7 @@ properties: x-google-marketplace: type: IMAGE INGRESS_AVAILABLE: - type: string + type: boolean title: Ingress Supported description: Indicates whether the cluster is detected to have Ingress support. x-google-marketplace: From a10cea523ef785d419f71adbd329084229179c08 Mon Sep 17 00:00:00 2001 From: eranchetz Date: Mon, 30 Dec 2019 10:52:29 +0200 Subject: [PATCH 029/120] fix typo in redis-ent-cluster yaml UI_SERVICE --- Makefile | 2 +- manifest/redis-enterprise-cluster.yaml.template | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 73f17b5..39293b3 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ include helper/gcloud.Makefile include helper/var.Makefile OP_VERSION=5.4.6-1186 -TAG ?= 1.11 +TAG ?= 1.12 REGISTRY ?= gcr.io/proven-reality-226706 METRICS_EXPORTER_TAG ?= v0.7.1 diff --git a/manifest/redis-enterprise-cluster.yaml.template b/manifest/redis-enterprise-cluster.yaml.template index 2c20c61..10c6f54 100644 --- a/manifest/redis-enterprise-cluster.yaml.template +++ b/manifest/redis-enterprise-cluster.yaml.template @@ -10,7 +10,7 @@ spec: persistentSpec: enabled: true storageClassName: "standard" - uiServiceType: ${UI_SERVICE_TYPE} + uiServiceType: ${UI_SERVICE} username: $REDIS_ADMIN redisEnterpriseNodeResources: limits: From d67d10d84eb3bc2b6408cce401aa68be9e329947 Mon Sep 17 00:00:00 2001 From: Joshua Fox <4542591+JoshuaFox@users.noreply.github.com> Date: Mon, 30 Dec 2019 14:48:33 +0200 Subject: [PATCH 030/120] TAG should be envsubst-ed --- Makefile | 2 +- README.md | 18 ++++++++++++------ manifest/application.yaml.template | 2 +- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index 39293b3..0983c71 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ include helper/gcloud.Makefile include helper/var.Makefile OP_VERSION=5.4.6-1186 -TAG ?= 1.12 +TAG ?= 1.13 REGISTRY ?= gcr.io/proven-reality-226706 METRICS_EXPORTER_TAG ?= v0.7.1 diff --git a/README.md b/README.md index c3f9d1a..4ea99f3 100644 --- a/README.md +++ b/README.md @@ -132,8 +132,15 @@ export NODE_MEM=1 Configure the container images. Update version numbers as necessary. ```shell -export IMAGE_REDIS=gcr.io/proven-reality-226706/redislabs:1.11 -export IMAGE_UBBAGENT=gcr.io/proven-reality-226706/redislabs/ubbagent:1.11 +export IMAGE_REDIS=gcr.io/proven-reality-226706/redislabs:$VERSION +export IMAGE_UBBAGENT=gcr.io/proven-reality-226706/redislabs/ubbagent:$VERSION +``` +Set the version + +**Note**: Do not use a patch number like 1.12.0; use only major-minor. + +```shell +export VERSION= ``` #### Create namespace in your Kubernetes cluster @@ -176,7 +183,7 @@ expanded manifest file for future updates to the application. ```shell awk 'FNR==1 {print "---"}{print}' manifest/* \ - | envsubst '$APP_INSTANCE_NAME $NAMESPACE $IMAGE_REDIS $REPLICAS $REDIS_ADMIN $SERVICE_ACCOUNT $IMAGE_UBBAGENT $NODE_CPU $NODE_MEM' \ + | envsubst '$APP_INSTANCE_NAME $NAMESPACE $IMAGE_REDIS $REPLICAS $REDIS_ADMIN $SERVICE_ACCOUNT $TAG $IMAGE_UBBAGENT $NODE_CPU $NODE_MEM' \ > "${APP_INSTANCE_NAME}_manifest.yaml" ``` @@ -299,9 +306,8 @@ gcloud container clusters delete "$CLUSTER" --zone "$ZONE" When a few version of the Redis Operator comes out, you will want to upgrade the version. 1. Upgrade `OP_VERSION=5.4.6-1186` in `Makefile`. -2. Increment `Makefile:TAG ?= 1.11` in `Makefile`. This will increment the version of both this Marketplace package and the UBB image that provides the sidecar. -**Note**: Do not use a patch number like 1.12.0; use only major-minor. +2. Increment `Makefile:TAG ?= $VERSION` in `Makefile`. This will increment the version of both this Marketplace package and the UBB image that provides the sidecar. 3. `make -B app/build` (where `-B` forces the build even if no change is detected). * This builds the image and pushes it to gcr.io -(`cloudbuild.yaml` allows building this in Cloud Buld instead of `make`. It is not yet in active use, pending permissions for triggers and adoption of a process. ) \ No newline at end of file +(`cloudbuild.yaml` allows building this in Cloud Buld instead of `make`. It is not yet in active use, pending permissions for triggers and adoption of a process. ) diff --git a/manifest/application.yaml.template b/manifest/application.yaml.template index 48070a8..e39eb62 100644 --- a/manifest/application.yaml.template +++ b/manifest/application.yaml.template @@ -12,7 +12,7 @@ metadata: spec: descriptor: type: Redis Enterprise Operator - version: $TAG + version: "$TAG" description: |- Redis Operator makes it easy to deploy and manage Redis Enterprise on Kubernetes. maintainers: From 1b7ecaab188508d28799a3c6d2eeadf86008d470 Mon Sep 17 00:00:00 2001 From: Joshua Fox <4542591+JoshuaFox@users.noreply.github.com> Date: Tue, 31 Dec 2019 16:43:22 +0200 Subject: [PATCH 031/120] order for envsubst --- deployer/deploy.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/deployer/deploy.sh b/deployer/deploy.sh index e2fd22f..ef09370 100755 --- a/deployer/deploy.sh +++ b/deployer/deploy.sh @@ -65,6 +65,12 @@ app_api_version=$(kubectl get "applications.app.k8s.io/$NAME" \ --namespace="$NAMESPACE" \ --output=jsonpath='{.apiVersion}') +shopt -s nocasematch +case "$INGRESS_AVAILABLE" in + "true" ) export UI_SERVICE=LoadBalancer ;; + *) export UI_SERVICE=ClusterIP ;; +esac + /bin/expand_config.py --values_mode raw --app_uid "$app_uid" create_manifests.sh @@ -89,12 +95,6 @@ export SERVICE_ACCOUNT="$(/bin/print_config.py \ echo "Admin Service Account = $SERVICE_ACCOUNT" -shopt -s nocasematch -case "$INGRESS_AVAILABLE" in - "true" ) export UI_SERVICE=LoadBalancer ;; - *) export UI_SERVICE=ClusterIP ;; -esac - # Put CRD in configmap so elvated Job can install it kubectl create configmap crd-cm --from-file=crd=/bin/crd.yaml # Create elavated job to create Job From 90e6288f76e245ba7d425e3c306abe7a192cfb0d Mon Sep 17 00:00:00 2001 From: Joshua Fox <4542591+JoshuaFox@users.noreply.github.com> Date: Tue, 31 Dec 2019 17:48:25 +0200 Subject: [PATCH 032/120] autoincrement version --- Makefile | 2 +- README.md | 4 +++- increment-version.py | 30 ++++++++++++++++++++++++++++++ manifest/application.yaml.template | 2 +- 4 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 increment-version.py diff --git a/Makefile b/Makefile index 0983c71..e82903e 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ include helper/gcloud.Makefile include helper/var.Makefile OP_VERSION=5.4.6-1186 -TAG ?= 1.13 +TAG ?= 1.14 REGISTRY ?= gcr.io/proven-reality-226706 METRICS_EXPORTER_TAG ?= v0.7.1 diff --git a/README.md b/README.md index 4ea99f3..c4261cf 100644 --- a/README.md +++ b/README.md @@ -218,7 +218,9 @@ kubectl port-forward redis-enterprise-cluster-0 8443 #### Getting the Admin Password -See [instructions here](https://docs.redislabs.com/latest/rs/faqs/). +See [instructions here](https://docs.redislabs.com/latest/rs/faqs/) under "How to retrieve the username/password for a Redis Enterprise Cluster?" + +In brief, `kubectl get secret redis-enterprise -o yaml|grep password|cut -d':' -f 2|base64 --decode` should get you the password, and you should already know the username (default admin@example.com) #### Access the Redis-Enterprise service externally diff --git a/increment-version.py b/increment-version.py new file mode 100644 index 0000000..666e264 --- /dev/null +++ b/increment-version.py @@ -0,0 +1,30 @@ +import re +import os +import sys + +def update(fn, start): + p = re.compile("1\.(\d+)") + with open(fn, "r") as fin: + with open(fn + "next", "w") as fout: + + def process(line): + if line.startswith(start): + m = p.search(line) + assert m, line + old_v=m.group(1) + v = int(old_v) + 1 + fout.write(f"{start}1.{v}\n") + + else: + fout.write(line) + + line = fin.readline() + process(line) + while line: + line = fin.readline() + process(line) + os.replace(fn + "next", fn) + + +update("Makefile", "TAG ?= ") +update("manifest/application.yaml.template", " version: ") diff --git a/manifest/application.yaml.template b/manifest/application.yaml.template index e39eb62..0ddd86d 100644 --- a/manifest/application.yaml.template +++ b/manifest/application.yaml.template @@ -12,7 +12,7 @@ metadata: spec: descriptor: type: Redis Enterprise Operator - version: "$TAG" + version: 1.14 description: |- Redis Operator makes it easy to deploy and manage Redis Enterprise on Kubernetes. maintainers: From 7d00c527910318495577c8538795a9ce1bde2268 Mon Sep 17 00:00:00 2001 From: Joshua Fox <4542591+JoshuaFox@users.noreply.github.com> Date: Wed, 1 Jan 2020 11:45:44 +0200 Subject: [PATCH 033/120] identify mismatched versions between files --- Makefile | 2 +- increment-version.py | 19 ++++++++++++++----- manifest/application.yaml.template | 2 +- 3 files changed, 16 insertions(+), 7 deletions(-) mode change 100644 => 100755 increment-version.py diff --git a/Makefile b/Makefile index e82903e..0983c71 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ include helper/gcloud.Makefile include helper/var.Makefile OP_VERSION=5.4.6-1186 -TAG ?= 1.14 +TAG ?= 1.13 REGISTRY ?= gcr.io/proven-reality-226706 METRICS_EXPORTER_TAG ?= v0.7.1 diff --git a/increment-version.py b/increment-version.py old mode 100644 new mode 100755 index 666e264..09b395e --- a/increment-version.py +++ b/increment-version.py @@ -1,19 +1,26 @@ +#!/usr/bin/env python3 + + import re import os import sys def update(fn, start): + old_v=None p = re.compile("1\.(\d+)") with open(fn, "r") as fin: with open(fn + "next", "w") as fout: def process(line): + nonlocal old_v if line.startswith(start): m = p.search(line) assert m, line - old_v=m.group(1) - v = int(old_v) + 1 - fout.write(f"{start}1.{v}\n") + assert old_v is None, old_v + old_v_s=m.group(1) + old_v=int(old_v_s) + new_v = old_v + 1 + fout.write(f"{start}1.{new_v}\n") else: fout.write(line) @@ -25,6 +32,8 @@ def process(line): process(line) os.replace(fn + "next", fn) + return old_v +old_v = update("Makefile", "TAG ?= ") +old_v2= update("manifest/application.yaml.template", " version: ") +assert old_v==old_v2, f"The input files had different versions before this script ran; please revert and fix:{old_v} != {old_v2} " -update("Makefile", "TAG ?= ") -update("manifest/application.yaml.template", " version: ") diff --git a/manifest/application.yaml.template b/manifest/application.yaml.template index 0ddd86d..b06213f 100644 --- a/manifest/application.yaml.template +++ b/manifest/application.yaml.template @@ -12,7 +12,7 @@ metadata: spec: descriptor: type: Redis Enterprise Operator - version: 1.14 + version: 1.13 description: |- Redis Operator makes it easy to deploy and manage Redis Enterprise on Kubernetes. maintainers: From 0006e3f1f700bed340a526adbbf167804f24488f Mon Sep 17 00:00:00 2001 From: Joshua Fox <4542591+JoshuaFox@users.noreply.github.com> Date: Wed, 1 Jan 2020 11:52:50 +0200 Subject: [PATCH 034/120] v.1.14 --- Makefile | 2 +- manifest/application.yaml.template | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 0983c71..e82903e 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ include helper/gcloud.Makefile include helper/var.Makefile OP_VERSION=5.4.6-1186 -TAG ?= 1.13 +TAG ?= 1.14 REGISTRY ?= gcr.io/proven-reality-226706 METRICS_EXPORTER_TAG ?= v0.7.1 diff --git a/manifest/application.yaml.template b/manifest/application.yaml.template index b06213f..0ddd86d 100644 --- a/manifest/application.yaml.template +++ b/manifest/application.yaml.template @@ -12,7 +12,7 @@ metadata: spec: descriptor: type: Redis Enterprise Operator - version: 1.13 + version: 1.14 description: |- Redis Operator makes it easy to deploy and manage Redis Enterprise on Kubernetes. maintainers: From 111ab59aed57147813a6d3453405b61b7f70b1af Mon Sep 17 00:00:00 2001 From: Joshua Fox <4542591+JoshuaFox@users.noreply.github.com> Date: Mon, 13 Jan 2020 14:23:44 +0200 Subject: [PATCH 035/120] v 1.15 --- Makefile | 2 +- manifest/application.yaml.template | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index e82903e..0b7336b 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ include helper/gcloud.Makefile include helper/var.Makefile OP_VERSION=5.4.6-1186 -TAG ?= 1.14 +TAG ?= 1.15 REGISTRY ?= gcr.io/proven-reality-226706 METRICS_EXPORTER_TAG ?= v0.7.1 diff --git a/manifest/application.yaml.template b/manifest/application.yaml.template index 0ddd86d..c6130df 100644 --- a/manifest/application.yaml.template +++ b/manifest/application.yaml.template @@ -12,7 +12,7 @@ metadata: spec: descriptor: type: Redis Enterprise Operator - version: 1.14 + version: 1.15 description: |- Redis Operator makes it easy to deploy and manage Redis Enterprise on Kubernetes. maintainers: From 1b8bc7b6ca5e0d2c07afe09e061df0390462b261 Mon Sep 17 00:00:00 2001 From: Joshua Fox <4542591+JoshuaFox@users.noreply.github.com> Date: Mon, 13 Jan 2020 14:32:31 +0200 Subject: [PATCH 036/120] v1.15 --- increment-version.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/increment-version.py b/increment-version.py index 09b395e..9603434 100755 --- a/increment-version.py +++ b/increment-version.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 - +# This increments the version number in two files. This will no be necessary once +# CI is set up wih variable replacement import re import os import sys @@ -33,6 +34,7 @@ def process(line): os.replace(fn + "next", fn) return old_v + old_v = update("Makefile", "TAG ?= ") old_v2= update("manifest/application.yaml.template", " version: ") assert old_v==old_v2, f"The input files had different versions before this script ran; please revert and fix:{old_v} != {old_v2} " From 0fbf42f2f7955082437a7c43fa0c83cb7a783a3b Mon Sep 17 00:00:00 2001 From: Joshua Fox <4542591+JoshuaFox@users.noreply.github.com> Date: Mon, 13 Jan 2020 14:50:00 +0200 Subject: [PATCH 037/120] v1.15 --- deployer/deploy.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/deployer/deploy.sh b/deployer/deploy.sh index ef09370..a600949 100755 --- a/deployer/deploy.sh +++ b/deployer/deploy.sh @@ -93,8 +93,6 @@ export SERVICE_ACCOUNT="$(/bin/print_config.py \ --values_mode raw)" echo "Admin Service Account = $SERVICE_ACCOUNT" - - # Put CRD in configmap so elvated Job can install it kubectl create configmap crd-cm --from-file=crd=/bin/crd.yaml # Create elavated job to create Job From 2e1fb4737f2c44dd5f7ccb6c395be8ade66737d5 Mon Sep 17 00:00:00 2001 From: Joshua Fox <4542591+JoshuaFox@users.noreply.github.com> Date: Mon, 13 Jan 2020 16:47:58 +0200 Subject: [PATCH 038/120] v.1.15 envsubst --- create_manifests.sh | 63 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100755 create_manifests.sh diff --git a/create_manifests.sh b/create_manifests.sh new file mode 100755 index 0000000..82ace18 --- /dev/null +++ b/create_manifests.sh @@ -0,0 +1,63 @@ +#!/bin/bash +# +# Copyright 2018 Google LLC +# +# 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. + +set -eox pipefail + +for i in "$@" +do +case $i in + --mode=*) + mode="${i#*=}" + shift + ;; + *) + >&2 echo "Unrecognized flag: $i" + exit 1 + ;; +esac +done + +[[ -z "$NAME" ]] && echo "NAME must be set" && exit 1 +[[ -z "$NAMESPACE" ]] && echo "NAMESPACE must be set" && exit 1 + +env_vars="$(/bin/print_config.py -o shell_vars)" + +echo "Creating the manifests for the kubernetes resources that build the application \"$NAME\"" + +data_dir="/data" +manifest_dir="$data_dir/manifest-expanded" +mkdir "$manifest_dir" + +# Overwrite the templates using the test templates +if [[ "$mode" = "test" ]]; then + if [[ -e "/data-test/manifest" ]]; then + cp -RT "/data-test/manifest" "/data/manifest" + else + echo "$LOG_SMOKE_TEST INFO No overriding manifests found at /data-test/manifest." + fi +fi + +# Replace the environment variables placeholders from the manifest templates +for manifest_template_file in "$data_dir"/manifest/*; do + manifest_file=$(basename "$manifest_template_file" | sed 's/.template$//') + cat "$manifest_template_file" \ + | /bin/config_env.py envsubst "${env_vars}" \ + > "$manifest_dir/${manifest_file}.tmp" + + cat "$manifest_dir/${manifest_file}.tmp" |envsubst '$UI_SERVICE' \ + > "$manifest_dir/${manifest_file}" + +done From 8d5dd0435ab20c6a8cc795cf939dfbcec0fbaa27 Mon Sep 17 00:00:00 2001 From: "Joshua T. Fox" <4542591+JoshuaFox@users.noreply.github.com> Date: Sun, 19 Jan 2020 12:36:48 +0200 Subject: [PATCH 039/120] Update README.md --- README.md | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index c4261cf..f72ede9 100644 --- a/README.md +++ b/README.md @@ -72,10 +72,20 @@ gcloud container clusters get-credentials "$CLUSTER" --zone "$ZONE" #### Clone this repo -Clone this repo and the associated tools repo. +Clone this repo + + +```shell +git clone https://github.com/RedisLabs/gkemarketplace +``` +Optional: For reference, you can get RedisLabs Enterprise K8s Operator code (i.e., unrelated to Google MP) +```shell +git clone https://github.com/RedisLabs/redis-enterprise-k8s-docs.git +``` +Optional: For reference, you can get MP K8s tools, examples, and instructions ```shell -git clone https://github.com/RedisLabs/redis-enterprise-k8s-docs.git +git clone https://github.com/GoogleCloudPlatform/marketplace-k8s-app-tools ``` #### Install the Application resource definition @@ -140,7 +150,7 @@ Set the version **Note**: Do not use a patch number like 1.12.0; use only major-minor. ```shell -export VERSION= +export VERSION=. ``` #### Create namespace in your Kubernetes cluster @@ -151,7 +161,7 @@ Run the command below to create a new namespace. It is idempotent. kubectl create namespace "$NAMESPACE" ``` -#### Prerequisites for using Role-Based Access Control +#### Prerequisite: Role-Based Access Control To use [role-based access control](https://kubernetes.io/docs/reference/access-authn-authz/rbac/) for the app, grant your user the ability to create roles in Kubernetes: @@ -166,8 +176,8 @@ For steps to enable role-based access control in Google Kubernetes Engine, see t #### Expand the manifest template -Use `envsubst` to expand the template. We recommend that you save the -expanded manifest file for future updates to the application. +We are using the `envsubst` approach to expand the template. (An alternative approach is with Helm and will be used soon.) +We recommend that you save the expanded manifest file for future updates to the application. 1. Expand `RBAC` YAML file. You can configure the RBAC first. @@ -183,7 +193,8 @@ expanded manifest file for future updates to the application. ```shell awk 'FNR==1 {print "---"}{print}' manifest/* \ - | envsubst '$APP_INSTANCE_NAME $NAMESPACE $IMAGE_REDIS $REPLICAS $REDIS_ADMIN $SERVICE_ACCOUNT $TAG $IMAGE_UBBAGENT $NODE_CPU $NODE_MEM' \ + | envsubst '$APP_INSTANCE_NAME $NAMESPACE $IMAGE_REDIS $REPLICAS $REDIS_ADMIN $SERVICE_ACCOUNT $TAG $IMAGE_ + AGENT $NODE_CPU $NODE_MEM' \ > "${APP_INSTANCE_NAME}_manifest.yaml" ``` From 93133d8c7f93c7d8cc8cd8c295ba031a93f47aee Mon Sep 17 00:00:00 2001 From: "Joshua T. Fox" <4542591+JoshuaFox@users.noreply.github.com> Date: Sun, 19 Jan 2020 12:41:30 +0200 Subject: [PATCH 040/120] Update README.md --- README.md | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index f72ede9..2e4cc7f 100644 --- a/README.md +++ b/README.md @@ -72,11 +72,15 @@ gcloud container clusters get-credentials "$CLUSTER" --zone "$ZONE" #### Clone this repo -Clone this repo - - +Required: Clone this repo. ```shell git clone https://github.com/RedisLabs/gkemarketplace +``` + +Required: Clone ubbagent (monitors for billing) +```shell +git clone https://github.com/RedisLabs/ubbagent.git + ``` Optional: For reference, you can get RedisLabs Enterprise K8s Operator code (i.e., unrelated to Google MP) ```shell @@ -316,11 +320,16 @@ gcloud container clusters delete "$CLUSTER" --zone "$ZONE" # Upgrading the version -When a few version of the Redis Operator comes out, you will want to upgrade the version. +When a new version of the Redis Operator comes out, you will want to upgrade the version. + +# Upgrade `OP_VERSION=5.4.6-1186` in `Makefile`. +# Push +# Increment `Makefile:TAG ?= $VERSION` in `Makefile`. This will increment the version of both this Marketplace package and the UBB image that provides the sidecar. +# In repo for `ubbagent` (`https://github.com/RedisLabs/ubbagent.git`), set `TAG` env variable to new version, e.g. 1.15, then + `docker build -t gcr.io/proven-reality-226706/redislabs/ubbagent:$TAG .` + `docker push gcr.io/proven-reality-226706/redislabs/ubbagent:$TAG` -1. Upgrade `OP_VERSION=5.4.6-1186` in `Makefile`. -2. Increment `Makefile:TAG ?= $VERSION` in `Makefile`. This will increment the version of both this Marketplace package and the UBB image that provides the sidecar. -3. `make -B app/build` (where `-B` forces the build even if no change is detected). +4. Back in `gkemarketplace` `make -B app/build` (where `-B` forces the build even if no change is detected). * This builds the image and pushes it to gcr.io (`cloudbuild.yaml` allows building this in Cloud Buld instead of `make`. It is not yet in active use, pending permissions for triggers and adoption of a process. ) From 7b7165952b968c8848a8a28ffb99208eea95e708 Mon Sep 17 00:00:00 2001 From: Joshua Fox <4542591+JoshuaFox@users.noreply.github.com> Date: Sun, 19 Jan 2020 16:24:07 +0200 Subject: [PATCH 041/120] Helm --- .gitignore | 1 + Dockerfile | 1 + Makefile | 89 +++++------ .../templates/tester.yaml} | 12 +- apptest/deployer/schema.yaml | 1 + apptest/tester/Dockerfile | 16 +- apptest/tester/tester.sh | 2 +- apptest/tester/tests/basic-suite.yaml | 76 ++++++++- chart/redis-operator/Chart.yaml | 2 + ...s.com_redisenterpriseclusters.app_crd.yaml | 7 +- chart/redis-operator/logo.png | Bin 0 -> 4852 bytes chart/redis-operator/templates/_helpers.tpl | 24 +++ .../redis-operator/templates/application.yaml | 45 ++++++ .../templates/configmap/crd.yaml | 13 ++ .../templates/deployment/operator.yaml | 41 +++++ .../templates/job/crd-create.yaml | 36 +++++ .../redis-operator/templates/rbac/rbac.yaml | 12 +- .../redis-enterprise-cluster.yaml | 49 ++++++ chart/redis-operator/values.yaml | 10 ++ cloudbuild.yaml | 18 --- create_manifests.sh | 63 -------- deployer/Dockerfile | 48 ++++-- deployer/deploy.sh | 118 -------------- deployer/deploy_with_tests.sh | 147 ------------------ deployer/install-job.yaml.template | 30 ---- helper/MARKETPLACE_TOOLS_TAG | 1 - helper/app.Makefile | 114 -------------- helper/common.Makefile | 43 ----- helper/crd.Makefile | 18 --- helper/gcloud.Makefile | 23 --- helper/var.Makefile | 65 -------- increment-version.py | 41 ----- manifest/application.yaml.template | 40 ----- manifest/operator.yaml.template | 33 ---- .../redis-enterprise-cluster.yaml.template | 48 ------ resources/service-accounts.yaml | 136 ++++++++++++++++ schema.yaml | 103 +++++++----- 37 files changed, 596 insertions(+), 930 deletions(-) create mode 100644 Dockerfile rename apptest/deployer/{manifest/tester.yaml.template => redis-operator/templates/tester.yaml} (70%) create mode 100644 chart/redis-operator/Chart.yaml rename deployer/crd.yaml => chart/redis-operator/files/crd/redislabs.com_redisenterpriseclusters.app_crd.yaml (83%) create mode 100644 chart/redis-operator/logo.png create mode 100644 chart/redis-operator/templates/_helpers.tpl create mode 100644 chart/redis-operator/templates/application.yaml create mode 100644 chart/redis-operator/templates/configmap/crd.yaml create mode 100644 chart/redis-operator/templates/deployment/operator.yaml create mode 100644 chart/redis-operator/templates/job/crd-create.yaml rename manifest/rbac.yaml.template => chart/redis-operator/templates/rbac/rbac.yaml (69%) create mode 100644 chart/redis-operator/templates/redis-enterprise-cluster/redis-enterprise-cluster.yaml create mode 100644 chart/redis-operator/values.yaml delete mode 100644 cloudbuild.yaml delete mode 100755 create_manifests.sh delete mode 100755 deployer/deploy.sh delete mode 100755 deployer/deploy_with_tests.sh delete mode 100644 deployer/install-job.yaml.template delete mode 100644 helper/MARKETPLACE_TOOLS_TAG delete mode 100644 helper/app.Makefile delete mode 100644 helper/common.Makefile delete mode 100644 helper/crd.Makefile delete mode 100644 helper/gcloud.Makefile delete mode 100644 helper/var.Makefile delete mode 100755 increment-version.py delete mode 100644 manifest/application.yaml.template delete mode 100644 manifest/operator.yaml.template delete mode 100644 manifest/redis-enterprise-cluster.yaml.template create mode 100644 resources/service-accounts.yaml diff --git a/.gitignore b/.gitignore index 30bcfa4..e94f7ce 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .build/ +.DS_Store diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ea7a9cd --- /dev/null +++ b/Dockerfile @@ -0,0 +1 @@ +FROM gcr.io/cloud-marketplace-tools/k8s/deployer_helm/onbuild diff --git a/Makefile b/Makefile index 0b7336b..c15d36d 100644 --- a/Makefile +++ b/Makefile @@ -1,65 +1,56 @@ -include helper/app.Makefile -include helper/crd.Makefile -include helper/gcloud.Makefile -include helper/var.Makefile +include ../crd.Makefile +include ../gcloud.Makefile +include ../var.Makefile +include ../images.Makefile -OP_VERSION=5.4.6-1186 -TAG ?= 1.15 -REGISTRY ?= gcr.io/proven-reality-226706 -METRICS_EXPORTER_TAG ?= v0.7.1 +VERIFY_WAIT_TIMEOUT = 1200 -$(info ---- TAG = $(TAG)) +CHART_NAME := redis-operator +APP_ID ?= $(CHART_NAME) -APP_DEPLOYER_IMAGE ?= $(REGISTRY)/redislabs/deployer:$(TAG) -NAME ?= redislabs-1 +#SOURCE_REGISTRY ?= marketplace.gcr.io/google -ifdef METRICS_EXPORTER_ENABLED - METRICS_EXPORTER_ENABLED_FIELD = , "metrics.enabled": "$(METRICS_EXPORTER_ENABLED)" -endif +SOURCE_REGISTRY ?= gcr.io +TRACK ?= 1.15 -APP_PARAMETERS ?= { \ - "APP_INSTANCE_NAME": "$(NAME)", \ - "NAMESPACE": "$(NAMESPACE)", \ - "REPORTING_SECRET": "test-value" \ - $(METRICS_EXPORTER_ENABLED_FIELD) \ -} -TESTER_IMAGE ?= $(REGISTRY)/redislabs/tester:$(TAG) +IMAGE_MAIN ?= $(SOURCE_REGISTRY)/proven-reality-226706/redislabs:$(TRACK) +IMAGE_DEPLOYER_HELM ?= gcr.io/cloud-marketplace-tools/k8s/deployer_helm:$(MARKETPLACE_TOOLS_TAG) + +# Main image +image-$(CHART_NAME) := $(call get_sha256,$(IMAGE_MAIN)) -app/build:: .build/redislabs/deployer \ - .build/redislabs/redislabs \ +# List of images used in application +ADDITIONAL_IMAGES := deployer-helm +# Additional images variable names should correspond with ADDITIONAL_IMAGES list +# Should be dynamically to use $(MARKETPLACE_TOOLS_TAG) +image-deployer-helm ?= $(call get_sha256,$(IMAGE_DEPLOYER_HELM)) -.build/redislabs: | .build - mkdir -p "$@" +C2D_CONTAINER_RELEASE := $(call get_c2d_release,$(image-$(CHART_NAME))) +BUILD_ID := $(shell date --utc +%Y%m%d-%H%M%S) +RELEASE ?= $(C2D_CONTAINER_RELEASE)-$(BUILD_ID) -.build/redislabs/deployer: deployer/* \ - schema.yaml \ - .build/var/APP_DEPLOYER_IMAGE \ - .build/var/MARKETPLACE_TOOLS_TAG \ - .build/var/REGISTRY \ - .build/var/TAG \ - | .build/redislabs - docker build \ - --build-arg REGISTRY="$(REGISTRY)/redislabs" \ - --build-arg TAG="$(TAG)" \ - --build-arg MARKETPLACE_TOOLS_TAG="$(MARKETPLACE_TOOLS_TAG)" \ - --tag "$(APP_DEPLOYER_IMAGE)" \ - -f deployer/Dockerfile \ - . - docker push "$(APP_DEPLOYER_IMAGE)" - @touch "$@" +$(info ---- TRACK = $(TRACK)) +$(info ---- RELEASE = $(RELEASE)) +$(info ---- SOURCE_REGISTRY = $(SOURCE_REGISTRY)) +APP_DEPLOYER_IMAGE ?= $(REGISTRY)/$(APP_ID)/deployer:$(RELEASE) +APP_DEPLOYER_IMAGE_TRACK_TAG ?= $(REGISTRY)/$(APP_ID)/deployer:$(TRACK) +APP_GCS_PATH ?= $(GCS_URL)/$(APP_ID)/$(TRACK) -.build/redislabs/redislabs: .build/var/REGISTRY \ - .build/var/TAG \ - | .build/redislabs - docker pull redislabs/operator:${OP_VERSION} - docker tag redislabs/operator:${OP_VERSION} \ - "$(REGISTRY)/redislabs:$(TAG)" - docker push "$(REGISTRY)/redislabs:$(TAG)" - @touch "$@" +NAME ?= $(APP_ID)-1 + +APP_PARAMETERS ?= { \ + "name": "$(NAME)", \ + "namespace": "$(NAMESPACE)" \ +} +# app_v2.Makefile provides the main targets for installing the application. +# It requires several APP_* variables defined above, and thus must be included after. +include ../ +# Build tester image +app/build:: .build/$(CHART_NAME)/tester diff --git a/apptest/deployer/manifest/tester.yaml.template b/apptest/deployer/redis-operator/templates/tester.yaml similarity index 70% rename from apptest/deployer/manifest/tester.yaml.template rename to apptest/deployer/redis-operator/templates/tester.yaml index 86d6bfe..f733a01 100644 --- a/apptest/deployer/manifest/tester.yaml.template +++ b/apptest/deployer/redis-operator/templates/tester.yaml @@ -6,19 +6,19 @@ apiVersion: v1 # kind: Job kind: Pod metadata: - name: "${APP_INSTANCE_NAME}-tester" + name: "{{ .Release.Name }}-tester" labels: - app.kubernetes.io/name: "${APP_INSTANCE_NAME}" + app.kubernetes.io/name: "{{ .Release.Name }}" annotations: marketplace.cloud.google.com/verification: test spec: # TODO(click-to-deploy/issues/324): Add deadline after migrating to Job # activeDeadlineSeconds: 1200 # TODO(click-to-deploy/issues/323): Remove following dependency on internal name - serviceAccountName: ${APP_INSTANCE_NAME}-deployer-sa + serviceAccountName: {{ .Values.operator.serviceAccountName }} containers: - name: tester - image: "${testerImage}" + image: "{{ .Values.testerImage }}" imagePullPolicy: Always env: - name: NAMESPACE @@ -26,5 +26,7 @@ spec: fieldRef: fieldPath: metadata.namespace - name: APP_INSTANCE_NAME - value: ${APP_INSTANCE_NAME} + value: {{ .Release.Name }} + initContainers: + {{- include "initContainerWaitForCRDsDeploy" . | nindent 4 }} restartPolicy: Never diff --git a/apptest/deployer/schema.yaml b/apptest/deployer/schema.yaml index 172fca0..20e22e9 100644 --- a/apptest/deployer/schema.yaml +++ b/apptest/deployer/schema.yaml @@ -1,5 +1,6 @@ properties: testerImage: type: string + default: $REGISTRY/tester:$TAG x-google-property: type: IMAGE diff --git a/apptest/tester/Dockerfile b/apptest/tester/Dockerfile index 960bcff..5dd3087 100644 --- a/apptest/tester/Dockerfile +++ b/apptest/tester/Dockerfile @@ -1,16 +1,14 @@ -FROM gcr.io/cloud-marketplace-tools/testrunner:0.1.0 +FROM gcr.io/cloud-marketplace-tools/testrunner:0.1.2 RUN apt-get update && apt-get install -y --no-install-recommends \ - ca-certificates \ - gettext \ - jq \ - uuid-runtime \ - wget \ + curl wget dnsutils netcat jq \ && rm -rf /var/lib/apt/lists/* -RUN wget -q -O /bin/kubectl \ - https://storage.googleapis.com/kubernetes-release/release/v1.12.0/bin/linux/amd64/kubectl \ - && chmod 755 /bin/kubectl +RUN mkdir -p /opt/kubectl/1.14 \ + && wget -q -O /opt/kubectl/1.14/kubectl \ + https://storage.googleapis.com/kubernetes-release/release/v1.14.8/bin/linux/amd64/kubectl \ + && chmod 755 /opt/kubectl/1.14/kubectl \ + && ln -s /opt/kubectl/1.14/kubectl /usr/bin/kubectl COPY tests/basic-suite.yaml /tests/basic-suite.yaml COPY tester.sh /tester.sh diff --git a/apptest/tester/tester.sh b/apptest/tester/tester.sh index ddccd4c..96957d4 100755 --- a/apptest/tester/tester.sh +++ b/apptest/tester/tester.sh @@ -1,6 +1,6 @@ #!/bin/bash # -# Copyright 2018 Google LLC +# Copyright 2019 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/apptest/tester/tests/basic-suite.yaml b/apptest/tester/tests/basic-suite.yaml index cdf72f4..31a7a25 100644 --- a/apptest/tester/tests/basic-suite.yaml +++ b/apptest/tester/tests/basic-suite.yaml @@ -1,8 +1,78 @@ actions: -- name: dummy test +- name: kubectl smoke test bashTest: - script: |- - exit 0 + script: kubectl version + expect: + exitCode: + equals: 0 +- name: Waiting for RedisEnterpriseClusters CRDs created + bashTest: + script: | + timeout 120 bash -c ' + until kubectl get crd app.redislabs.com; + do echo "Waiting for RedisEnterpriseClusters CRDs created"; sleep 5; + done' + expect: + exitCode: + equals: 0 +- name: Deploy test RedisEnterpriseCluster + bashTest: + script: | + kubectl apply --namespace ${NAMESPACE} -f - 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 aUH%`nB#Ylhr*r850000- + data:image/png;base64, {{ .Files.Get "logo.png" | b64enc }} {{/* Get logo.png from the root of chart */}} + marketplace.cloud.google.com/deploy-info: '{"partner_id": "redislabs-public", "product_id": {{ .Chart.Name | quote }}, "partner_name": "Redis Labs"}' + labels: + app.kubernetes.io/name: "{{ .Release.Name }}" +spec: + descriptor: + type: Redis Enterprise Operator + version: {{ .Values.operator.image.tag | quote }} + description: |- + Redis Operator makes it easy to deploy and manage Redis Enterprise + on Kubernetes. + maintainers: + - name: Redis Labs + url: https://redislabs.com/company/contact/support/ + links: + - description: 'User Guide: Redis Enterprise' + url: https://support.redislabs.com + notes: |- + See more details and manual installation instructions here https://github.com/RedisLabs/gkemarketplace + selector: + matchLabels: + app.kubernetes.io/name: "{{ .Release.Name }}" + componentKinds: + - group: apps/v1 + kind: Deployment + - group: v1 + kind: ConfigMap + - group: v1 + kind: ServiceAccount + - group: v1 + kind: Job + - group: v1 + kind: Pod + - group: v1 + kind: Job + - group: v1 + kind: Service + - group: apps/v1 + kind: StatefulSet diff --git a/chart/redis-operator/templates/configmap/crd.yaml b/chart/redis-operator/templates/configmap/crd.yaml new file mode 100644 index 0000000..f96d9ed --- /dev/null +++ b/chart/redis-operator/templates/configmap/crd.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "redis_operator.CRDsConfigMap" . }} + labels: + app.kubernetes.io/name: {{ .Release.Name }} + app.kubernetes.io/component: crd-configmap +data: + {{- $root := . -}} + {{- range $path, $bytes := .Files.Glob "files/crd/*.yaml" }} + {{ base $path }}: |- + {{- $root.Files.Get $path | nindent 4 }} + {{- end -}} diff --git a/chart/redis-operator/templates/deployment/operator.yaml b/chart/redis-operator/templates/deployment/operator.yaml new file mode 100644 index 0000000..345ff04 --- /dev/null +++ b/chart/redis-operator/templates/deployment/operator.yaml @@ -0,0 +1,41 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "redis_operator.DeploymentName" . }} + labels: + app.kubernetes.io/name: "{{ .Release.Name }}" + app.kubernetes.io/component: operator +spec: + replicas: 1 + selector: + matchLabels: + name: {{ template "redis_operator.DeploymentName" . }} + template: + metadata: + labels: + name: {{ template "redis_operator.DeploymentName" . }} + app.kubernetes.io/name: "{{ .Release.Name }}" + app.kubernetes.io/component: operator + spec: + serviceAccountName: {{ .Values.operator.serviceAccountName }} + initContainers: + {{- include "initContainerWaitForCRDsDeploy" . | nindent 6 }} + containers: + - name: redis-enterprise-operator + image: {{ .Values.operator.image.repository }}:{{ .Values.operator.image.tag }} + command: + - redis-enterprise-operator + imagePullPolicy: Always + env: + - name: WATCH_NAMESPACE + value: "" + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: OPERATOR_NAME + value: "{{ template "redis_operator.DeploymentName" . }}" diff --git a/chart/redis-operator/templates/job/crd-create.yaml b/chart/redis-operator/templates/job/crd-create.yaml new file mode 100644 index 0000000..1c9d9dc --- /dev/null +++ b/chart/redis-operator/templates/job/crd-create.yaml @@ -0,0 +1,36 @@ +apiVersion: batch/v1 +kind: Job +metadata: + annotations: + name: {{ template "redis_operator.CRDsJob" . }} + labels: + app.kubernetes.io/name: "{{ .Release.Name }}" + app.kubernetes.io/component: crd-job +spec: + ttlSecondsAfterFinished: 300 + backoffLimit: 0 + completions: 1 + parallelism: 1 + template: + spec: + containers: + - command: + - "/bin/bash" + - "-ec" + - | + for crd in /crd_to_create/*; + do kubectl apply -f $crd; + done + image: {{ .Values.deployerHelm.image }} + imagePullPolicy: Always + name: crd-create + volumeMounts: + - name: crd-configmap + mountPath: /crd_to_create/ + dnsPolicy: ClusterFirst + restartPolicy: Never + serviceAccountName: {{ .Values.CDRJobServiceAccount }} + volumes: + - name: crd-configmap + configMap: + name: {{ template "redis_operator.CRDsConfigMap" . }} diff --git a/manifest/rbac.yaml.template b/chart/redis-operator/templates/rbac/rbac.yaml similarity index 69% rename from manifest/rbac.yaml.template rename to chart/redis-operator/templates/rbac/rbac.yaml index 3019f5a..d2c66b8 100644 --- a/manifest/rbac.yaml.template +++ b/chart/redis-operator/templates/rbac/rbac.yaml @@ -3,7 +3,7 @@ kind: Role apiVersion: rbac.authorization.k8s.io/v1 metadata: name: redis-enterprise-operator - namespace: $NAMESPACE + namespace: {{ .Release.Namespace }} rules: - apiGroups: ["", "extensions", "apps", "rbac.authorization.k8s.io", "policy"] resources: ["*"] @@ -16,18 +16,18 @@ rules: kind: ServiceAccount apiVersion: v1 metadata: - name: $SERVICE_ACCOUNT - namespace: $NAMESPACE + name: {{ .Values.operator.serviceAccountName }} + namespace: {{ .Release.Namespace }} --- kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: redis-enterprise-operator - namespace: $NAMESPACE + namespace: {{ .Release.Namespace }} subjects: - kind: ServiceAccount - name: $SERVICE_ACCOUNT - namespace: $NAMESPACE + name: {{ .Values.operator.serviceAccountName }} + namespace: {{ .Release.Namespace }} roleRef: kind: Role name: redis-enterprise-operator diff --git a/chart/redis-operator/templates/redis-enterprise-cluster/redis-enterprise-cluster.yaml b/chart/redis-operator/templates/redis-enterprise-cluster/redis-enterprise-cluster.yaml new file mode 100644 index 0000000..117adbd --- /dev/null +++ b/chart/redis-operator/templates/redis-enterprise-cluster/redis-enterprise-cluster.yaml @@ -0,0 +1,49 @@ +apiVersion: "app.redislabs.com/v1" +kind: "RedisEnterpriseCluster" +metadata: + name: "redis-enterprise" + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: "{{ .Release.Name }}" +spec: + nodes: {{ .Values.operator.replicas }} + persistentSpec: + enabled: true + storageClassName: "standard" + uiServiceType: ClusterIP + #TODO uiServiceType: if INGRESS_AVAILABLE + username: {{ .Values.operator.redisAdmin }} + redisEnterpriseNodeResources: + limits: + cpu: "{{ .Values.operator.nodeCpu }}m" + memory: {{ .Values.operator.nodeMem }}Gi + requests: + cpu: "{{ .Values.operator.nodeCpu }}m" + memory: {{ .Values.operator.nodeMem }}Gi +# sideContainersSpec:#TODO +# - name: ubbagent +# image: "{{ .Values.operator.ubbAgent }}" +# imagePullPolicy: IfNotPresent +# env: +# - name: NODE_CPU +# value: "{{ .Values.operator.nodeCpu }}m" +# - name: NODE_MEM +# value: {{ .Values.operator.nodeMem }}Gi +# - name: AGENT_CONFIG_FILE +# value: /etc/ubbagent/config.yaml +# - name: AGENT_LOCAL_PORT +# value: "6080" +# - name: AGENT_STATE_DIR +# value: /opt/persistent/ubbagent +# - name: AGENT_REPORT_DIR +# value: /opt/persistent/ubbagent/reports +# - name: AGENT_ENCODED_KEY +# valueFrom: +# secretKeyRef: +# name: "{{ .Release.Name }}-reporting-secret" +# key: reporting-key +# - name: AGENT_CONSUMER_ID +# valueFrom: +# secretKeyRef: +# name: "{{ .Release.Name }}-reporting-secret" +# key: consumer-id diff --git a/chart/redis-operator/values.yaml b/chart/redis-operator/values.yaml new file mode 100644 index 0000000..c7d8561 --- /dev/null +++ b/chart/redis-operator/values.yaml @@ -0,0 +1,10 @@ +operator: + serviceAccountName: null #TODO null here follows jaeger example. Is that right? + image: + repository: null + tag: null + replicas: 3 + redisAdmin: admin@example.com + nodeCpu: 4000 + nodeMem: 15 + ubbAgent: TODO diff --git a/cloudbuild.yaml b/cloudbuild.yaml deleted file mode 100644 index a50093c..0000000 --- a/cloudbuild.yaml +++ /dev/null @@ -1,18 +0,0 @@ -steps: -- name: gcr.io/cloud-builders/docker - id: docker-build-push - entrypoint: bash - args: - - '-c' - - | - export newver=`echo "$TAG_NAME" | cut -d"<" -f 1` - export opver=`echo "$TAG_NAME"| cut -d"<" -f 2` - echo 'new version' $newver 'from' $opver - docker build --build-arg REGISTRY=gcr.io/${PROJECT_ID}/redislabs --build-arg TAG=${newver} --build-arg MARKETPLACE_TOOLS_TAG="0.7.9" --tag gcr.io/${PROJECT_ID}/redislabs/deployer:${newver} -f deployer/Dockerfile . - docker push gcr.io/${PROJECT_ID}/redislabs/deployer:${newver} - docker pull redislabs/operator:$opver - docker tag redislabs/operator:$opver gcr.io/${PROJECT_ID}/redislabs:${newver} - docker push gcr.io/${PROJECT_ID}/redislabs:${newver} - -options: - substitution_option: 'ALLOW_LOOSE' diff --git a/create_manifests.sh b/create_manifests.sh deleted file mode 100755 index 82ace18..0000000 --- a/create_manifests.sh +++ /dev/null @@ -1,63 +0,0 @@ -#!/bin/bash -# -# Copyright 2018 Google LLC -# -# 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. - -set -eox pipefail - -for i in "$@" -do -case $i in - --mode=*) - mode="${i#*=}" - shift - ;; - *) - >&2 echo "Unrecognized flag: $i" - exit 1 - ;; -esac -done - -[[ -z "$NAME" ]] && echo "NAME must be set" && exit 1 -[[ -z "$NAMESPACE" ]] && echo "NAMESPACE must be set" && exit 1 - -env_vars="$(/bin/print_config.py -o shell_vars)" - -echo "Creating the manifests for the kubernetes resources that build the application \"$NAME\"" - -data_dir="/data" -manifest_dir="$data_dir/manifest-expanded" -mkdir "$manifest_dir" - -# Overwrite the templates using the test templates -if [[ "$mode" = "test" ]]; then - if [[ -e "/data-test/manifest" ]]; then - cp -RT "/data-test/manifest" "/data/manifest" - else - echo "$LOG_SMOKE_TEST INFO No overriding manifests found at /data-test/manifest." - fi -fi - -# Replace the environment variables placeholders from the manifest templates -for manifest_template_file in "$data_dir"/manifest/*; do - manifest_file=$(basename "$manifest_template_file" | sed 's/.template$//') - cat "$manifest_template_file" \ - | /bin/config_env.py envsubst "${env_vars}" \ - > "$manifest_dir/${manifest_file}.tmp" - - cat "$manifest_dir/${manifest_file}.tmp" |envsubst '$UI_SERVICE' \ - > "$manifest_dir/${manifest_file}" - -done diff --git a/deployer/Dockerfile b/deployer/Dockerfile index d2f360c..7858887 100644 --- a/deployer/Dockerfile +++ b/deployer/Dockerfile @@ -1,19 +1,41 @@ -FROM gcr.io/cloud-marketplace-tools/k8s/deployer_envsubst:latest +ARG MARKETPLACE_TOOLS_TAG -COPY manifest/* /data/manifest/ -ADD schema.yaml /data/ +FROM marketplace.gcr.io/google/debian9 AS build + +ARG CHART_NAME + +RUN apt-get update \ + && apt-get install -y --no-install-recommends gettext + +ADD chart/$CHART_NAME /tmp/chart +RUN cd /tmp && tar -czvf /tmp/$CHART_NAME.tar.gz chart + +ADD apptest/deployer/$CHART_NAME /tmp/test/chart +RUN cd /tmp/test \ + && tar -czvf /tmp/test/$CHART_NAME.tar.gz chart/ + +ADD schema.yaml /tmp/schema.yaml # Provide registry prefix and tag for default values for images. -ARG REGISTRY=gcr.io/proven-reality-226706/redislabs -ARG TAG=0.2.0 +ARG REGISTRY +ARG TAG -# Replace the original deploy with our deploy.sh that installs crd as first stage -ADD deployer/deploy.sh /bin/ -ADD deployer/deploy_with_tests.sh /bin/ -ADD deployer/install-job.yaml.template /bin/ -ADD deployer/crd.yaml /bin/ +RUN cat /tmp/schema.yaml \ + | env -i "REGISTRY=$REGISTRY" "TAG=$TAG" envsubst \ + > /tmp/schema.yaml.new \ + && mv /tmp/schema.yaml.new /tmp/schema.yaml -RUN cat /data/schema.yaml \ +ADD apptest/deployer/schema.yaml /tmp/apptest/schema.yaml +RUN cat /tmp/apptest/schema.yaml \ | env -i "REGISTRY=$REGISTRY" "TAG=$TAG" envsubst \ - > /data/schema.yaml.new \ - && mv /data/schema.yaml.new /data/schema.yaml + > /tmp/apptest/schema.yaml.new \ + && mv /tmp/apptest/schema.yaml.new /tmp/apptest/schema.yaml + +FROM gcr.io/cloud-marketplace-tools/k8s/deployer_helm:$MARKETPLACE_TOOLS_TAG + +ARG CHART_NAME + +COPY --from=build /tmp/$CHART_NAME.tar.gz /data/chart/ +COPY --from=build /tmp/test/$CHART_NAME.tar.gz /data-test/chart/ +COPY --from=build /tmp/apptest/schema.yaml /data-test/ +COPY --from=build /tmp/schema.yaml /data/ diff --git a/deployer/deploy.sh b/deployer/deploy.sh deleted file mode 100755 index a600949..0000000 --- a/deployer/deploy.sh +++ /dev/null @@ -1,118 +0,0 @@ -#!/bin/bash -# -# Copyright 2018 Google LLC -# -# 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. - -set -eox pipefail - -# This is the entry point for the production deployment - -# If any command returns with non-zero exit code, set -e will cause the script -# to exit. Prior to exit, set App assembly status to "Failed". -handle_failure() { - code=$? - if [[ -z "$NAME" ]] || [[ -z "$NAMESPACE" ]]; then - # /bin/expand_config.py might have failed. - # We fall back to the unexpanded params to get the name and namespace. - NAME="$(/bin/print_config.py \ - --xtype NAME \ - --values_mode raw)" - NAMESPACE="$(/bin/print_config.py \ - --xtype NAMESPACE \ - --values_mode raw)" - export NAME - export NAMESPACE - fi - patch_assembly_phase.sh --status="Failed" - exit $code -} -trap "handle_failure" EXIT - -NAME="$(/bin/print_config.py \ - --xtype NAME \ - --values_mode raw)" -NAMESPACE="$(/bin/print_config.py \ - --xtype NAMESPACE \ - --values_mode raw)" -export NAME -export NAMESPACE - -echo "Checking other deployments" -# make sure the operator is not deployed in this namespace and if it does fail the deployment -PREV_DEPLOY=$(kubectl get deploy redis-enterprise-operator --n $NAMESPACE -o name || true) -if [[ ! -z "$PREV_DEPLOY" ]]; then - echo "Cannot deploy, there is a redis operator already running in namespace $NAMESPACE" - exit $? -fi - -echo "Deploying application \"$NAME\"" - -app_uid=$(kubectl get "applications.app.k8s.io/$NAME" \ - --namespace="$NAMESPACE" \ - --output=jsonpath='{.metadata.uid}') -app_api_version=$(kubectl get "applications.app.k8s.io/$NAME" \ - --namespace="$NAMESPACE" \ - --output=jsonpath='{.apiVersion}') - -shopt -s nocasematch -case "$INGRESS_AVAILABLE" in - "true" ) export UI_SERVICE=LoadBalancer ;; - *) export UI_SERVICE=ClusterIP ;; -esac - -/bin/expand_config.py --values_mode raw --app_uid "$app_uid" - -create_manifests.sh - -# Assign owner references for the resources. -/bin/set_ownership.py \ - --app_name "$NAME" \ - --app_uid "$app_uid" \ - --app_api_version "$app_api_version" \ - --manifests "/data/manifest-expanded" \ - --dest "/data/resources.yaml" - -# Ensure assembly phase is "Pending", until successful kubectl apply. -/bin/setassemblyphase.py \ - --manifest "/data/resources.yaml" \ - --status "Pending" - -export SERVICE_ACCOUNT="$(/bin/print_config.py \ - --xtype SERVICE_ACCOUNT \ - --values_mode raw)" - -echo "Admin Service Account = $SERVICE_ACCOUNT" -# Put CRD in configmap so elvated Job can install it -kubectl create configmap crd-cm --from-file=crd=/bin/crd.yaml -# Create elavated job to create Job -envsubst < /bin/install-job.yaml.template > /bin/install-job.yaml -cat /bin/install-job.yaml -kubectl create -f /bin/install-job.yaml -# Wait for CRD job to finish and be available and then Apply the manifest. -sleep 10 -CRDREADY=`kubectl get job redis-crd-installer -o jsonpath="{.status.succeeded}"` -while [[ ${CRDREADY} != 1 ]] ; do - echo waiting for CRD job to complete - sleep 2 - CRDREADY=`kubectl get job redis-crd-installer -o jsonpath="{.status.succeeded}"` -done - -# Apply the manifest. -kubectl apply --namespace="$NAMESPACE" --filename="/data/resources.yaml" - -patch_assembly_phase.sh --status="Success" - -clean_iam_resources.sh - -trap - EXIT diff --git a/deployer/deploy_with_tests.sh b/deployer/deploy_with_tests.sh deleted file mode 100755 index d6bc434..0000000 --- a/deployer/deploy_with_tests.sh +++ /dev/null @@ -1,147 +0,0 @@ -#!/bin/bash -# -# Copyright 2018 Google LLC -# -# 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. - -set -eox pipefail - -# This is the entry point for the test deployment - -# If any command returns with non-zero exit code, set -e will cause the script -# to exit. Prior to exit, set App assembly status to "Failed". -handle_failure() { - code=$? - if [[ -z "$NAME" ]] || [[ -z "$NAMESPACE" ]]; then - # /bin/expand_config.py might have failed. - # We fall back to the unexpanded params to get the name and namespace. - NAME="$(/bin/print_config.py \ - --xtype NAME \ - --values_mode raw)" - NAMESPACE="$(/bin/print_config.py \ - --xtype NAMESPACE \ - --values_mode raw)" - export NAME - export NAMESPACE - fi - patch_assembly_phase.sh --status="Failed" - exit $code -} -trap "handle_failure" EXIT - -LOG_SMOKE_TEST="SMOKE_TEST" -test_schema="/data-test/schema.yaml" -overlay_test_schema.py \ - --test_schema "$test_schema" \ - --original_schema "/data/schema.yaml" \ - --output "/data/schema.yaml" - -NAME="$(/bin/print_config.py \ - --xtype NAME \ - --values_mode raw)" -NAMESPACE="$(/bin/print_config.py \ - --xtype NAMESPACE \ - --values_mode raw)" -export NAME -export NAMESPACE - -echo "Checking other deployments" -# make sure the operator is not deployed in this namespace and if it does fail the deployment -PREV_DEPLOY=$(kubectl get deploy redis-enterprise-operator --n $NAMESPACE -o name || true) -if [[ ! -z "$PREV_DEPLOY" ]]; then - echo "Cannot deploy, there is a redis operator already running in namespace $NAMESPACE" - exit $? -fi - -echo "Deploying application \"$NAME\" in test mode" - -app_uid=$(kubectl get "applications.app.k8s.io/$NAME" \ - --namespace="$NAMESPACE" \ - --output=jsonpath='{.metadata.uid}') -app_api_version=$(kubectl get "applications.app.k8s.io/$NAME" \ - --namespace="$NAMESPACE" \ - --output=jsonpath='{.apiVersion}') - -/bin/expand_config.py --values_mode raw --app_uid "$app_uid" - -create_manifests.sh --mode="test" - -# Assign owner references for the resources. -/bin/set_ownership.py \ - --app_name "$NAME" \ - --app_uid "$app_uid" \ - --app_api_version "$app_api_version" \ - --manifests "/data/manifest-expanded" \ - --dest "/data/resources.yaml" - -separate_tester_resources.py \ - --app_uid "$app_uid" \ - --app_name "$NAME" \ - --app_api_version "$app_api_version" \ - --manifests "/data/resources.yaml" \ - --out_manifests "/data/resources.yaml" \ - --out_test_manifests "/data/tester.yaml" - -export SERVICE_ACCOUNT="$(/bin/print_config.py \ - --xtype SERVICE_ACCOUNT \ - --values_mode raw)" - -echo "Admin Service Account = $SERVICE_ACCOUNT" - - -shopt -s nocasematch -case "$INGRESS_AVAILABLE" in - "true" ) export UI_SERVICE=LoadBalancer ;; - *) export UI_SERVICE=ClusterIP ;; -esac - -# Put CRD in configmap so elvated Job can install it -kubectl create configmap crd-cm --from-file=crd=/bin/crd.yaml -# Create elavated job to create Job -envsubst < /bin/install-job.yaml.template > /bin/install-job.yaml -cat /bin/install-job.yaml -kubectl create -f /bin/install-job.yaml -# Wait for CRD job to finish and be available and then Apply the manifest. -sleep 10 -CRDREADY=`kubectl get job redis-crd-installer -o jsonpath="{.status.succeeded}"` -while [[ ${CRDREADY} != 1 ]] ; do - echo waiting for CRD job to complete - sleep 2 - CRDREADY=`kubectl get job redis-crd-installer -o jsonpath="{.status.succeeded}"` -done - -# Apply the manifest. -kubectl apply --namespace="$NAMESPACE" --filename="/data/resources.yaml" - -patch_assembly_phase.sh --status="Success" - -wait_for_ready.py \ - --name $NAME \ - --namespace $NAMESPACE \ - --timeout ${WAIT_FOR_READY_TIMEOUT:-300} - -tester_manifest="/data/tester.yaml" -if [[ -e "$tester_manifest" ]]; then - cat $tester_manifest - - run_tester.py \ - --namespace $NAMESPACE \ - --manifest $tester_manifest \ - --timeout ${TESTER_TIMEOUT:-300} -else - echo "$LOG_SMOKE_TEST No tester manifest found at $tester_manifest." -fi - -clean_iam_resources.sh - -trap - EXIT diff --git a/deployer/install-job.yaml.template b/deployer/install-job.yaml.template deleted file mode 100644 index cc0d0e3..0000000 --- a/deployer/install-job.yaml.template +++ /dev/null @@ -1,30 +0,0 @@ -# This creates a post-deploy job that acts as a second stage installer. -# Please read deploy.sh for more information. -apiVersion: batch/v1 -kind: Job -metadata: - name: redis-crd-installer - namespace: $NAMESPACE - labels: - app.kubernetes.io/name: "$APP_INSTANCE_NAME" -spec: - template: - spec: - serviceAccountName: $SERVICE_ACCOUNT - containers: - - name: redis-installer - image: gcr.io/cloud-marketplace-tools/k8s/deployer_envsubst:latest - command: ["/bin/bash", "-c", "kubectl apply --force=true -f /data/crd.yaml"] - imagePullPolicy: Always - volumeMounts: - - name: config-volume - mountPath: /data/ - restartPolicy: Never - volumes: - - name: config-volume - configMap: - name: crd-cm - items: - - key: crd - path: crd.yaml - backoffLimit: 0 diff --git a/helper/MARKETPLACE_TOOLS_TAG b/helper/MARKETPLACE_TOOLS_TAG deleted file mode 100644 index 972ef76..0000000 --- a/helper/MARKETPLACE_TOOLS_TAG +++ /dev/null @@ -1 +0,0 @@ -0.7.9 diff --git a/helper/app.Makefile b/helper/app.Makefile deleted file mode 100644 index 44759b1..0000000 --- a/helper/app.Makefile +++ /dev/null @@ -1,114 +0,0 @@ -ifndef __APP_MAKEFILE__ - -__APP_MAKEFILE__ := included - - -makefile_dir := $(dir $(realpath $(lastword $(MAKEFILE_LIST)))) -include $(makefile_dir)/common.Makefile -include $(makefile_dir)/var.Makefile - -VERIFY_WAIT_TIMEOUT = 600 - -# Extracts the name property from APP_PARAMETERS. -define name_parameter -$(shell echo '$(APP_PARAMETERS)' \ - | docker run -i --entrypoint=/bin/print_config.py --rm $(APP_DEPLOYER_IMAGE) --values_mode stdin --xtype NAME) -endef - - -# Extracts the namespace property from APP_PARAMETERS. -define namespace_parameter -$(shell echo '$(APP_PARAMETERS)' \ - | docker run -i --entrypoint=/bin/print_config.py --rm $(APP_DEPLOYER_IMAGE) --values_mode stdin --xtype NAMESPACE) -endef - - -##### Helper targets ##### - - -.build/app: | .build - mkdir -p "$@" - - -# Always update the dev script to make sure it's up to date. -# There isn't currently a way to detect if the dev container has changed. -.PHONY: .build/app/dev -.build/app/dev: .build/var/MARKETPLACE_TOOLS_TAG \ - | .build/app - docker run \ - "gcr.io/cloud-marketplace-tools/k8s/dev:$(MARKETPLACE_TOOLS_TAG)" \ - cat /scripts/dev > "$@" - chmod a+x "$@" - - -########### Main targets ########### - - -# Builds the application containers and push them to the registry. -# Including Makefile can extend this target. This target is -# a prerequisite for install. -.PHONY: app/build -app/build:: ; - - -# Installs the application into target namespace on the cluster. -.PHONY: app/install -app/install:: app/build \ - .build/var/APP_DEPLOYER_IMAGE \ - .build/var/APP_PARAMETERS \ - .build/var/MARKETPLACE_TOOLS_TAG \ - | .build/app/dev - $(call print_target) - .build/app/dev \ - /scripts/install \ - --deployer='$(APP_DEPLOYER_IMAGE)' \ - --parameters='$(APP_PARAMETERS)' \ - --entrypoint="/bin/deploy.sh" - - -# Installs the application into target namespace on the cluster. -.PHONY: app/install-test -app/install-test:: app/build \ - .build/var/APP_DEPLOYER_IMAGE \ - .build/var/APP_PARAMETERS \ - .build/var/MARKETPLACE_TOOLS_TAG \ - | .build/app/dev - $(call print_target) - .build/app/dev \ - /scripts/install \ - --deployer='$(APP_DEPLOYER_IMAGE)' \ - --parameters='$(APP_PARAMETERS)' \ - --entrypoint="/bin/deploy_with_tests.sh" - - -# Uninstalls the application from the target namespace on the cluster. -.PHONY: app/uninstall -app/uninstall: .build/var/APP_DEPLOYER_IMAGE \ - .build/var/APP_PARAMETERS - $(call print_target) - kubectl delete 'application/$(call name_parameter)' \ - --namespace='$(call namespace_parameter)' \ - --ignore-not-found - -# Runs the verification pipeline. -.PHONY: app/verify -app/verify: app/build \ - .build/var/APP_DEPLOYER_IMAGE \ - .build/var/APP_PARAMETERS \ - .build/var/MARKETPLACE_TOOLS_TAG \ - | .build/app/dev - $(call print_target) - .build/app/dev \ - /scripts/verify \ - --deployer='$(APP_DEPLOYER_IMAGE)' \ - --parameters='$(APP_PARAMETERS)' \ - --wait_timeout="$(VERIFY_WAIT_TIMEOUT)" - - -# Runs diagnostic tool to make sure your environment is properly setup. -app/doctor: | .build/app/dev - $(call print_target) - .build/app/dev /scripts/doctor.py - - -endif diff --git a/helper/common.Makefile b/helper/common.Makefile deleted file mode 100644 index b8c1c8e..0000000 --- a/helper/common.Makefile +++ /dev/null @@ -1,43 +0,0 @@ -ifndef __COMMON_MAKEFILE__ - -__COMMON_MAKEFILE__ := included - - -define print_target - @$(call print_notice,Building $@...) -endef - - -define print_notice - printf "\n\033[93m\033[1m$(1)\033[0m\n" -endef - - -define print_error - printf "\n\033[93m\033[1m$(1)\033[0m\n" -endef - - -# MARKETPLACE_TOOLS_TAG is the tag of the container images published -# by marketplace-k8s-app-tools. -tag_from_file := $(shell cat "$(dir $(realpath $(lastword $(MAKEFILE_LIST))))/MARKETPLACE_TOOLS_TAG") -MARKETPLACE_TOOLS_TAG ?= $(tag_from_file) -export MARKETPLACE_TOOLS_TAG - -$(info ---- MARKETPLACE_TOOLS_TAG = $(MARKETPLACE_TOOLS_TAG)) - - -.build: - mkdir -p "$@" - - -.build/tmp: | .build - mkdir -p "$@" - - -.PHONY: clean -clean:: - rm -Rf .build - - -endif diff --git a/helper/crd.Makefile b/helper/crd.Makefile deleted file mode 100644 index feb77d7..0000000 --- a/helper/crd.Makefile +++ /dev/null @@ -1,18 +0,0 @@ -ifndef __CRD_MAKEFILE__ - -__CRD_MAKEFILE__ := included - - -# Installs the application CRD on the cluster. -.PHONY: crd/install -crd/install: - kubectl apply -f "https://raw.githubusercontent.com/GoogleCloudPlatform/marketplace-k8s-app-tools/master/crd/app-crd.yaml" - - -# Uninstalls the application CRD from the cluster. -.PHONY: crd/uninstall -crd/uninstall: - kubectl delete -f "https://raw.githubusercontent.com/GoogleCloudPlatform/marketplace-k8s-app-tools/master/crd/app-crd.yaml" - - -endif diff --git a/helper/gcloud.Makefile b/helper/gcloud.Makefile deleted file mode 100644 index 4fe9e4f..0000000 --- a/helper/gcloud.Makefile +++ /dev/null @@ -1,23 +0,0 @@ -ifndef __GCLOUD_MAKEFILE__ - -__GCLOUD_MAKEFILE__ := included - -# Include this Makefile to automatically derive registry -# and target namespace from the current gcloud and kubectl -# configurations. -# This is for convenience over having to specifying the -# environment variables manually. - - - -ifndef NAMESPACE - NAMESPACE := $(shell kubectl config view -o jsonpath="{.contexts[?(@.name==\"$$(kubectl config current-context)\")].context.namespace}") - ifeq ($(NAMESPACE),) - NAMESPACE = default - endif -endif - -$(info ---- REGISTRY = $(REGISTRY)) -$(info ---- NAMESPACE = $(NAMESPACE)) - -endif diff --git a/helper/var.Makefile b/helper/var.Makefile deleted file mode 100644 index 9149af6..0000000 --- a/helper/var.Makefile +++ /dev/null @@ -1,65 +0,0 @@ -ifndef __VAR_MAKEFILE__ - -__VAR_MAKEFILE__ := included - -# Provides a class of targets that ensures variables are -# defined and trigger rebuilds when variable values change. -# -# Usage: -# -# my_target: .build/var/REGISTRY -# -# my_target rebuilds when the value of $(REGISTRY) changes. -# .build/var/REGISTRY also ensures that $(REGISTRY) value -# is non-empty. - - -# The main target that this Makefile offers. -# This is a real file that gets updated when a variable value -# change is detected. This rule does not have a recipe. It -# relies on the %-phony prerequisite to detect the change and -# update the file. -.build/var/%: .build/var/%-required .build/var/%-phony ; - - -.build/var: - mkdir -p .build/var - - -# Since we can't make pattern targets phony, we make them -# effectively phony by depending on this phony target. -.PHONY: var/phony -var/phony: ; - - -# An effectively phony target that always runs to compare the current -# value of the variable (say VARNAME) with the content of the -# corresponding .build/var/VARNAME file. If the contents differ, -# the recipe updates .build/var/VARNAME, which effectively trigger -# rebuilding of targets depending on .build/var.VARNAME. -.build/var/%-phony: var/phony | .build/var - @ \ - var_key="$*" ; \ - var_val="${$*}" ; \ - var_val_old=$$(cat ".build/var/$$var_key" 2> /dev/null) ; \ - if [ "$$var_val" != "$$var_val_old" ]; then \ - $(call print_notice,$$var_key has been updated) ; \ - printf " old value: $$var_val_old\n" ; \ - printf " new value: $$var_val\n" ; \ - printf "$$var_val" > .build/var/$$var_key ; \ - fi - - -# An effectively phony target that verifies that the variable -# has a non-empty value. -.build/var/%-required: var/phony - @ \ - var_key="$*" ; \ - var_val="${$*}" ; \ - if [ "$$var_val" = "" ]; then \ - $(call print_notice,Make variable '$*' is required); \ - exit 1; \ - fi - - -endif diff --git a/increment-version.py b/increment-version.py deleted file mode 100755 index 9603434..0000000 --- a/increment-version.py +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env python3 - -# This increments the version number in two files. This will no be necessary once -# CI is set up wih variable replacement -import re -import os -import sys - -def update(fn, start): - old_v=None - p = re.compile("1\.(\d+)") - with open(fn, "r") as fin: - with open(fn + "next", "w") as fout: - - def process(line): - nonlocal old_v - if line.startswith(start): - m = p.search(line) - assert m, line - assert old_v is None, old_v - old_v_s=m.group(1) - old_v=int(old_v_s) - new_v = old_v + 1 - fout.write(f"{start}1.{new_v}\n") - - else: - fout.write(line) - - line = fin.readline() - process(line) - while line: - line = fin.readline() - process(line) - os.replace(fn + "next", fn) - - return old_v - -old_v = update("Makefile", "TAG ?= ") -old_v2= update("manifest/application.yaml.template", " version: ") -assert old_v==old_v2, f"The input files had different versions before this script ran; please revert and fix:{old_v} != {old_v2} " - diff --git a/manifest/application.yaml.template b/manifest/application.yaml.template deleted file mode 100644 index c6130df..0000000 --- a/manifest/application.yaml.template +++ /dev/null @@ -1,40 +0,0 @@ ---- -apiVersion: app.k8s.io/v1beta1 -kind: Application -metadata: - name: "$APP_INSTANCE_NAME" - annotations: - kubernetes-engine.cloud.google.com/icon: >- - data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIIAAACCCAYAAACKAxD9AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3wIFEy8OiWavSwAAIABJREFUeAEAN4DIfwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA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BQMAEAQFAPX9/QDz/PwA8/z8APf+/gD8//8AAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAABAQD6//8A7Pr6APD7+wALBAMADQQEAA4DBAAIAwIABAICAAEAAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAP8AAAD7/v4A9f39APH7/ADy/PwA9/7+AAAAAAACAQAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAAAAMBAQAIAgIADQMDAA4EBAALBAQABQIBAP8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAPz+/wD5/v0A8fz8APD7+wD1/f4A/wAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAQIA+//+AO37+wD3/v4AEAQEAA8FBAAIAwMABAICAP///wD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAEAQEADgIEAAwFBAAS/f0A9fz9APP8/AD0/f0A+f7+AP4AAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAQEA/QAAAPL8/QD0/P0A7gL7AA4EBAAOBAQACgQDAAYCAgABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEAAP3+/wD5/v8A9f39APL7/ADz/fwA+f7+AP8AAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAAD/AAAAAv//AAUDAwAKAgIADgQEAAwEAwAJAwMABAEBAAAAAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAPz//gD2/P4A8vz8APP8/AD2/v0A/f8AAAEAAAABAAAA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAPX9/QD4/f0A8fz8AAwDAwAPBQQACgMEAAUCAQAAAAAA/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAAAAAAAAAgAAAAkDAwAIAwMADgUEAPf9/QDz/P0A9Pz8APb+/gD8/v4AAAAAAAIBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAP8AAAD3/v4A+fr6APL8/AAJAwMADAMDAAoDAgAGAgIAAwICAAIAAAAAAAAA/v//APv//wD6/f4A9f79APX8/QD3/v4A+/7+AP8AAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAAD+AAAAAwAAAAkCAgAMAwMADQUFAAoDAgAHAgIAAgEBAAAAAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAA/wAAAPr+/gD2/fwA8fuwfuByAAAgAElEQVT8APT9/gD4/f0A/gAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADJfzaABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAPP9/QD3/f0A8vz8AAsEAwAOBAQACwQEAAcBAQABAQEA/wAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAYCAgAGAgIADAQEAPf9BAD2/P0A8/38APX8/QD5//8A/gAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAP8AAAD3/v4A+P7+APP7/AAGAwEABQICAAQAAQADAQEA/wAAAP3//wD8//8A+v7+APv//gD8/wAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/gAAAAD//wAEAQEADAMDAA0EAwAOBAQACAMDAAUCAgABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAEAAAD/AAAA9/7+APT8/ADz/PwA9Pz9APr+/gD9AAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAP0AAAD0/v4A9vn9APL8/QAKA/wADgQEAAwEAwAIAgIAAgEBAAAAAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAQEADQQEAAsEBAAP/QQA9vz+APL9/AD1/fwA+P3+APz//wAAAQEAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAA+wD/APv9/wAA/P4AAQD/AAEBAQD/AAAAAP//AP4BAQAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAP8AAAAGAQEACgQDAA4EBQAMBAMACQMDAAMBAQABAAAA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAD9AAAA+f39APT8/ADy/P0A9f39APr+/gD/AAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAQEA9/7+APj9/QD0/fwACvsDAA0DBAAMBQMACAIDAAQCAQABAAAA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAAAAAAAAAQAAAAgDAwAIAwMADf8DAPf8BQDy/PwA8/39APn9/QD7//4A/gABAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAABAAAAAAEA/wAAAAAAAAAAAQEAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAEAAAAIAgIACgMCAA4EBQALAwMACAMCAAMBAQD/AAAAAAAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAP8AAAD7//8A9/z9APX9/ADy/P0A9v3+APv//gD/AAAAAgAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAEBAPn+/wD5/fwA8vz8AAr8AwANAwMADQUEAAgCAgAGAgIAAgEBAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAAAAAAGAQIABgUCAAwEAwD3/f4A9Pz7APP8/QD2/v4A+v7+AP4AAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAAAAIBAQAJAgEACwMDAA4EBQAMBAMABwIDAAEA/wD/AAAA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAP4AAAD6/v4A9/39APP8/QDy/PwA9/79APz/AAABAAAAAgAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAABAAD+AAAA8vz8APX9/QDw/PwACwMDAA0EAwAKBAQACAICAAIBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP4AAAAAAAAAAgEBAAwEAwAKBAMAEP7+APX8/ADz/P0A9f39APj+/gD8//8AAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAAAAQBAQAJAgIADAQEAA4EAwALAwMABAICAAAAAAD/AP8A/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP7//wD6/v4A9f39APP8/QD0/PwA+P7+AP3/AAACAQAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAADAQEAAAAAAPb9/gD3/f4A8fz8AAkD+wANBAQADAMDAAoDAwAEAgIAAQAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAA/wAAAAAAAAAHAgIABwYGAA4F/wD4/f0A8/z9APP8/AD2/f0A+///AP8AAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAAAAYBAQALAwMADgQEAAwEAwAHAgMABAIBAAAAAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAPz//wD3/f0A9fz8APL8/QD1/f4A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AAAAAAAAAAAAAAAAAAAABAEBAAwDAwAKCQMAEwYGAPL8+wDw+/wA9fz8APwAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP4AAAD2/f4A9v35AOz6+wAPBQMAEAQFAAsEBAAEAgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//AAD5/v0A8Pr7APD8/AD1/PwA/wEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAMBAQAMAwMADwQDAA8FBQAHAwMAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+P39APD7+wDt+vsA9/7+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgABAP0A/wDs+vsA9v39ABEFBQAQBQUACAICAAIBAQD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAIAwIACAIGABEFBAD1/f0A8fv9APP8/AD5/v4A/wAAAP8AAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+f/+AOv6/gDv+/wADAMDAA8EBAANBQQABgIDAAIBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAA/wAAAP///wD4/v4A8Pv7APD7/AD2/f0A/gAAAAEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAAABAAAAAAAAAAYBAQAMBAQAEAQDAAsEBQAGAgIAAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8A9v7+APL7+wDw+/wA9v7+AP7//wABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD1/f4A+P75APL7/AANBAQADgUEAAgCAwAEAgEAAQAAAAAAAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEABgEDAAUCAwAMA/4A+P4FAPP8/ADz/P0A9v39APz//wD//wAAAAEAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAPv//wDw/PwA8/39AAv8+wAOBAUADQQDAAcDAgAEAAIAAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/f//AP3//wD4/f4A8/38APL7/AD3/v4A/P//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAAAAAABAAAAAwAAAAgDAgANAwQADgQEAAoEAwAEAQEAAQEBAAEAAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAP8AAAD8/v8A9v39APL8/ADy+/sA+P//AP7//wD/AAAAAAAAAAEAAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAD9/wAA8v39APb9/QDx/PwADAMDAA0FBQAKAwIABQEBAAIBAQAAAAAA/wAAAAAAAAAAAAAAAAAAAP8AAAAAAAAAAQAAAAUCAgAFAgIACgMDABAF/gD1/fwA8/v9APX9/QD6/v4A/QAAAP8AAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAP8AAAD8//8A8vz9APX9/gDw+wIACgQDAAoCAwAHAwIABAEBAAEAAQAAAAAA/v/+APsA/wD4/f4A9vz9APb9/QD5//4A/f8AAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAP///wACAQEABQEBAAoEAwANAwMACwQEAAoCAgADAgIAAQAAAP8AAAAAAAAA/wAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAP7//wD6/v8A9f39APP8+wD0/f0A+P7/AP7//wAAAAAAAAAAAAEAAAAAAAAA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAA/AAAAPT9/QD2/v4A8Pz8AAwEAwANAwQADAQDAAYBAgADAgEAAAAAAAAAAAD/AAAAAAAAAAAAAAD/AAAAAAAAAAAAAAADAQEACgMDAAgCAgAPBAAA9f38APT8/QD0/fwA9/3+APz//wD/AAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAD/AAAA/P//APP9/QD1/f4AAwH7AAUBAQADAQEAAgEBAAAAAAD9//8A/P//APv+/gD+AAAA/wAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAP8AAAADAAAABgMCAAsDBAANBAQADAQDAAcCAgABAQEAAgAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAA/wAAAP3//wD5/v4A9Pz9APL8/AD2/PwA+v//AP4AAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAP7//wD3//8A+Pr+APEA+wAKBAQADgMEAA0FBAAIAwEAAwECAAEAAAD/AAAAAAAAAAAAAAAAAAAAAAAAAP8AAAAAAAAAAQEBAAUBAAAQAgUADgQEAPf9BQD1/PwA8vz8APX9/QD7AAAA////AAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAP4A/wD+/wAA+///AAAAAAABAAAAAAAAAP8AAAAAAAAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAEAAAADAAAABwICAA0EAwAOBQUACwMDAAUCAgABAAAA/wAAAAAAAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPz//gD3/f8A9Pz8APL8/AD1/f0A/P7+AAABAQABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAEAAAAAAAAA+wAAAPD8/ADy/PwACvwDAA0EAwAPBAQACQMDAAQBAgABAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAQEACwQDAAsDAgASBQUA8/39APD8+wD0/P0A+v//AP7//wD/AAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAEAAAAEAQAACAIDAA8EAwAOBQUACgMDAAIBAQAAAAAAAAAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//APv//wD4/v0A8fv8APL8/QD3/f0A/QAAAAEAAAABAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAQAAAAAAAAD1/f0A9f39AO36+wAOBAQAEAQFAAsEAgAEAgIAAgEBAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAAQDAQASAgEADwQDAPf+/gDx+/wA8vz8APb+/gD+//8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAEAAAACgQDABAEBQAPBgUACAEBAAABAQD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/v//APz//wD1/f0A8vv7APH8/QD4/v4AAAAAAAIAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAA+///AOz7+wDw/PwACwICAA8FBAANBAQABwICAAMBAQABAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8BAQACAQEADAICAAsEBAAT/v4A8/v7APH7/AD1/f0A+///AP4AAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAIAAAAFAQEADAQEABAFBAANBAQABQEBAAABAQD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/f7+APr//wDz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AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//////wAAP/rAAAA0gD+/8EA/f/BAPz72F3w+O8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKQTDB3/BAItAAABPwABATgAAQAoAQAAEgABAQQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD+AAAAAAAAAAMBAAAEAAEABgICAAMCAQACAAAA/wAAAP/+AAD7AP4A+/7/APv//wD///8AAAEBAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAA//cA/v/j////zAH/AMD9AP/J8/T422z2+/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgEAkRAgUFIwABADgBAgE/AAEBMwAAARsBAQAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAP8AAAAA//8AAAEBAAD/AAAAAQAAAP//AAAAAAABAQEAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////X/AP/XAv//xQD/AL3//v7S/uv0418AAP4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAng0EBwMJCRcBAAIuAQAARQADAT0AAAAqAQEBBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//+8A///N/f7+vQL+/73+AgLdX+ry7gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMMBwIACAcL/wD8HwADBEQBAQFIAAEBNwEBARAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//AAAA/+n+AADBAv3/s//9/cX8/v7oYe/39wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAChCwAB/QkMBgP9/hMBBQI3AQEDSwABAEQBAQAfAAABAAABAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//8A/wAAAAD//9sA//+4AP7/tPsA/sz4/ALzau/z+wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHgQBwIpBAQNAQICKwABAUgBAQFFAAAAMAEBAQYBAQEC/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////AAAA+QD/ANL///+3/wD8uP37+dQD/g/2XvDt/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAohcKCwD/ASABAAI+AAICRQEBATYAAAAUAAEBBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD7AP//8f///84AAP64Af0AvP79/d9e7vb0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJ4OCAgDBgQaAQMCMwEAAUABAQA7/wEBHwEBAQ4AAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD5AP7/6wAA/8j///+7/v3+xPj9/eBn7/f2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAADXjuTZAAAgAElEQVQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACcDAYECAkHE/4AACsBAgE9AQEBPgABASwAAQETAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD7AP//5gAAAMT//f66//79xfv+BOVj7vH4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAjQAAARYVCg8A/gIf/wQCPgECAUMBAAE0AAEBFgAA/wUAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAA/wD4AP8A6P8A/8gA//+1AAAAwvv8++li7ff5AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAChFQwI/wIAFgP/Aj8BAgFI/wEANQAAARcAAAAJAQABBAABAAEAAAAA/////gAAAPsAAAD1Af//5AAAAMf///66Af7/yP3+/+tf7fX7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnxgQAgf49wv9BwUr/wADNwIAADL/AAEeAQH/Cv4BAP8B/wDv////3QAAAMz/+vrLBwH/3foHB/pe5/L+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsQAAAvUSAgoA/gQE+/0AAAL0A/ld//f3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACObUCAAAAinSURBVAAAAAAAAAAAAAAAAAGSCG33AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJ6rTUEjcY3eAAAAAElFTkSuQmCC - marketplace.cloud.google.com/deploy-info: '{"partner_id": "redislabs-public", "product_id": "redis-enterprise", "partner_name": "Redis Labs"}' - labels: - app.kubernetes.io/name: "$APP_INSTANCE_NAME" -spec: - descriptor: - type: Redis Enterprise Operator - version: 1.15 - description: |- - Redis Operator makes it easy to deploy and manage Redis Enterprise on Kubernetes. - maintainers: - - name: Redis Labs - url: https://redislabs.com/company/contact/support/ - links: - - description: 'User Guide: Redis Enterprise' - url: https://support.redislabs.com - notes: |- - See more details and manual installation instructions here https://github.com/RedisLabs/gkemarketplace - matchLabels: - app.kubernetes.io/name: "$APP_INSTANCE_NAME" - componentKinds: - - group: apps/v1beta2 - kind: Deployment - - group: v1 - kind: Service - - group: apps/v1beta2 - kind: StatefulSet - - group: batch/v1 - kind: Job - - group: v1 - kind: Service - - group: v1 - kind: ServiceAccount diff --git a/manifest/operator.yaml.template b/manifest/operator.yaml.template deleted file mode 100644 index 8d26128..0000000 --- a/manifest/operator.yaml.template +++ /dev/null @@ -1,33 +0,0 @@ -apiVersion: apps/v1beta1 -kind: Deployment -metadata: - name: redis-enterprise-operator - namespace: $NAMESPACE - labels: - app.kubernetes.io/name: "$APP_INSTANCE_NAME" -spec: - replicas: 1 - selector: - matchLabels: - name: redis-enterprise-operator - template: - metadata: - labels: - name: redis-enterprise-operator - spec: - serviceAccount: $SERVICE_ACCOUNT - containers: - - name: redis-enterprise-operator - image: $IMAGE_REDIS - command: - - redis-enterprise-operator - imagePullPolicy: Always - env: - - name: WATCH_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name diff --git a/manifest/redis-enterprise-cluster.yaml.template b/manifest/redis-enterprise-cluster.yaml.template deleted file mode 100644 index 10c6f54..0000000 --- a/manifest/redis-enterprise-cluster.yaml.template +++ /dev/null @@ -1,48 +0,0 @@ -apiVersion: "app.redislabs.com/v1alpha1" -kind: "RedisEnterpriseCluster" -metadata: - name: "redis-enterprise" - namespace: $NAMESPACE - labels: - app.kubernetes.io/name: "$APP_INSTANCE_NAME" -spec: - nodes: $REPLICAS - persistentSpec: - enabled: true - storageClassName: "standard" - uiServiceType: ${UI_SERVICE} - username: $REDIS_ADMIN - redisEnterpriseNodeResources: - limits: - cpu: "${NODE_CPU}m" - memory: ${NODE_MEM}Gi - requests: - cpu: "${NODE_CPU}m" - memory: ${NODE_MEM}Gi - sideContainersSpec: - - name: ubbagent - image: $IMAGE_UBBAGENT - imagePullPolicy: IfNotPresent - env: - - name: NODE_CPU - value: "$NODE_CPU" - - name: NODE_MEM - value: "$NODE_MEM" - - name: AGENT_CONFIG_FILE - value: /etc/ubbagent/config.yaml - - name: AGENT_LOCAL_PORT - value: "6080" - - name: AGENT_STATE_DIR - value: /opt/persistent/ubbagent - - name: AGENT_REPORT_DIR - value: /opt/persistent/ubbagent/reports - - name: AGENT_ENCODED_KEY - valueFrom: - secretKeyRef: - name: "${APP_INSTANCE_NAME}-reporting-secret" - key: reporting-key - - name: AGENT_CONSUMER_ID - valueFrom: - secretKeyRef: - name: "${APP_INSTANCE_NAME}-reporting-secret" - key: consumer-id diff --git a/resources/service-accounts.yaml b/resources/service-accounts.yaml new file mode 100644 index 0000000..e9956e7 --- /dev/null +++ b/resources/service-accounts.yaml @@ -0,0 +1,136 @@ +# CRD creator service account +apiVersion: v1 +kind: ServiceAccount +metadata: + name: $CRD_SERVICE_ACCOUNT +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: $CRD_SERVICE_ACCOUNT-role +rules: +- apiGroups: + - apiextensions.k8s.io + resources: + - customresourcedefinitions + verbs: + - get + - list + - create +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: $CRD_SERVICE_ACCOUNT-rb +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: $CRD_SERVICE_ACCOUNT-role +subjects: +- kind: ServiceAccount + name: $CRD_SERVICE_ACCOUNT + namespace: $NAMESPACE +--- + +# Operator service account +apiVersion: v1 +kind: ServiceAccount +metadata: + name: $OPERATOR_SERVICE_ACCOUNT +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: $OPERATOR_SERVICE_ACCOUNT-role +rules: +- apiGroups: ["apiextensions.k8s.io"] + resources: ["customresourcedefinitions"] + verbs: ["get","list"] +- apiGroups: + - "" + resources: + - pods + - services + - endpoints + - persistentvolumeclaims + - events + - configmaps + - secrets + - serviceaccounts + verbs: + - '*' +- apiGroups: + - apps + resources: + - deployments + - daemonsets + - replicasets + - statefulsets + verbs: + - '*' +- apiGroups: + - monitoring.coreos.com + resources: + - servicemonitors + verbs: + - get + - create +- apiGroups: + - extensions + resources: + - replicasets + - deployments + - daemonsets + - statefulsets + - ingresses + verbs: + - '*' +- apiGroups: + - batch + resources: + - jobs + - cronjobs + verbs: + - '*' +#- apiGroups: +# - jaegertracing.io +# resources: +# - '*' +# verbs: +# - '*' +- apiGroups: + - rbac.authorization.k8s.io + resources: + - clusterrolebindings + verbs: + - '*' +- apiGroups: + - apps + - extensions + resourceNames: + - redis-operator + resources: + - deployments/finalizers + verbs: + - update +- apiGroups: + - kafka.strimzi.io + resources: + - kafkas + - kafkausers + verbs: + - '*' + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: $OPERATOR_SERVICE_ACCOUNT-rb +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: $OPERATOR_SERVICE_ACCOUNT-role +subjects: +- kind: ServiceAccount + name: $OPERATOR_SERVICE_ACCOUNT + namespace: $NAMESPACE diff --git a/schema.yaml b/schema.yaml index ea96840..8f7f17b 100644 --- a/schema.yaml +++ b/schema.yaml @@ -1,25 +1,39 @@ -application_api_version: v1beta1 +x-google-marketplace: + schemaVersion: v2 + applicationApiVersion: v1beta1 + publishedVersion: "$TAG" + publishedVersionMetadata: + releaseNote: >- + A regular update. + releaseTypes: + - Feature + recommended: false + + images: + '': + properties: + operator.image.repository: + type: REPO_WITH_REGISTRY + operator.image.tag: + type: TAG + clusterConstraints: + resources: + - replicas: 3 + requests: + cpu: 4000m + memory: 15Gi properties: - APP_INSTANCE_NAME: + name: type: string x-google-marketplace: type: NAME - NAMESPACE: + namespace: type: string x-google-marketplace: type: NAMESPACE - IMAGE_REDIS: - type: string - default: $REGISTRY:$TAG - x-google-marketplace: - type: IMAGE - SERVICE_ACCOUNT: - title: Cluster Admin Service Account - description: >- - Name of a service account in the target namespace that - has cluster-admin permissions. This is needed for the operator - to create REDIS CRD and resources. + operator.serviceAccountName: type: string + title: Service account used by Redis Operator x-google-marketplace: type: SERVICE_ACCOUNT serviceAccount: @@ -27,25 +41,25 @@ properties: - type: ClusterRole rulesType: PREDEFINED rulesFromRoleName: cluster-admin - REPLICAS: + operator.replicas: type: integer title: Number of Cluster Nodes description: The number of Pods in the solution default: 3 minimum: 3 maximum: 11 - REDIS_ADMIN: + operator.redisAdmin: type: string title: Redis admin username description: Username to be used for connecting to Redis # https://www.w3.org/TR/html52/sec-forms.html#email-state-typeemail pattern: ^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$ default: admin@example.com - IMAGE_UBBAGENT: - type: string - default: $REGISTRY/ubbagent:$TAG - x-google-marketplace: - type: IMAGE + # IMAGE_UBBAGENT: TODO + #type: string + #default: #"$REGISTRY/ubbagent:$TAG" + #x-google-marketplace: + # type: IMAGE INGRESS_AVAILABLE: type: boolean title: Ingress Supported @@ -56,35 +70,42 @@ properties: type: string x-google-marketplace: type: REPORTING_SECRET - NODE_CPU: + operator.nodeCpu: #TODO title: Node CPU [millis] type: integer description: Each node CPU in millicpu i.e. 1000 equals 1vCPU - default: 4000 + default: 4000 minimum: 100 maximum: 32000 - NODE_MEM: + operator.nodeMem: #TODO title: Node Memory [GB] type: integer description: Each node RAM in GB ie 1 equals 1GiB default: 15 minimum: 1 maximum: 269 + CDRJobServiceAccount: + type: string + title: Service account used by CRDs deployer + x-google-marketplace: + type: SERVICE_ACCOUNT + serviceAccount: + roles: + - type: ClusterRole + rulesType: CUSTOM + rules: + - apiGroups: ["apiextensions.k8s.io"] + resources: ["customresourcedefinitions"] + verbs: ["get","list","create"] + deployerHelm.image: + type: string + x-google-marketplace: + type: DEPLOYER_IMAGE required: -- APP_INSTANCE_NAME -- NAMESPACE -- IMAGE_REDIS -- SERVICE_ACCOUNT -- REPLICAS -- REDIS_ADMIN -- REPORTING_SECRET -- IMAGE_UBBAGENT -- NODE_CPU -- NODE_MEM -x-google-marketplace: - clusterConstraints: - resources: - - replicas: 3 - requests: - cpu: 4000m - memory: 15Gi +- name +- namespace +- operator.redisAdmin +- operator.replicas +- operator.reportingSecret +- operator.nodeCpu +- operator.nodeMem \ No newline at end of file From d3e51024c535e57953b5628ca7a43f130dfe40d0 Mon Sep 17 00:00:00 2001 From: Joshua Fox <4542591+JoshuaFox@users.noreply.github.com> Date: Sun, 26 Jan 2020 11:33:28 +0200 Subject: [PATCH 042/120] Helm --- chart/redis-operator/templates/deployment/operator.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/chart/redis-operator/templates/deployment/operator.yaml b/chart/redis-operator/templates/deployment/operator.yaml index 345ff04..ad566d8 100644 --- a/chart/redis-operator/templates/deployment/operator.yaml +++ b/chart/redis-operator/templates/deployment/operator.yaml @@ -2,6 +2,7 @@ apiVersion: apps/v1 kind: Deployment metadata: name: {{ template "redis_operator.DeploymentName" . }} + namespace: {{ .Release.Namespace }} # In Helm Jaeger, this is missing labels: app.kubernetes.io/name: "{{ .Release.Name }}" app.kubernetes.io/component: operator @@ -28,6 +29,7 @@ spec: imagePullPolicy: Always env: - name: WATCH_NAMESPACE + # In Helm Jaeger, value is just "". But in old RedisOperator and old Jaeger, we have value from fieldRef from metadata.namespace value: "" - name: POD_NAME valueFrom: From d49f0b4b6db0273456b427820530e3820753379d Mon Sep 17 00:00:00 2001 From: Joshua Fox <4542591+JoshuaFox@users.noreply.github.com> Date: Sun, 26 Jan 2020 11:45:06 +0200 Subject: [PATCH 043/120] Helm --- .gitignore | 1 + MARKETPLACE_TOOLS_TAG | 1 + app.Makefile | 116 ++++++++ app_v2.Makefile | 146 ++++++++++ c2d_deployer.Makefile | 137 ++++++++++ common.Makefile | 43 +++ crd.Makefile | 20 ++ gcloud.Makefile | 38 +++ images.Makefile | 10 + redis-enterprise_manifest.yaml | 255 ++++++++++++++++++ redis-enterprise_sa_manifest.yaml | 136 ++++++++++ Makefile => redis/Makefile | 0 README.md => redis/README.md | 0 .../redis-operator/templates/tester.yaml | 0 .../apptest}/deployer/schema.yaml | 0 {apptest => redis/apptest}/tester/Dockerfile | 0 {apptest => redis/apptest}/tester/tester.sh | 0 .../apptest}/tester/tests/basic-suite.yaml | 0 .../chart}/redis-operator/Chart.yaml | 0 ...s.com_redisenterpriseclusters.app_crd.yaml | 0 .../chart}/redis-operator/logo.png | Bin .../redis-operator/templates/_helpers.tpl | 0 .../redis-operator/templates/application.yaml | 0 .../templates/configmap/crd.yaml | 0 .../templates/deployment/operator.yaml | 0 .../templates/job/crd-create.yaml | 0 .../redis-operator/templates/rbac/rbac.yaml | 0 .../redis-enterprise-cluster.yaml | 0 .../chart}/redis-operator/values.yaml | 0 {deployer => redis/deployer}/Dockerfile | 0 .../resources}/service-accounts.yaml | 0 schema.yaml => redis/schema.yaml | 0 Dockerfile => tmp_Dockerfile | 0 var.Makefile | 65 +++++ 34 files changed, 968 insertions(+) create mode 100644 MARKETPLACE_TOOLS_TAG create mode 100644 app.Makefile create mode 100644 app_v2.Makefile create mode 100644 c2d_deployer.Makefile create mode 100644 common.Makefile create mode 100644 crd.Makefile create mode 100644 gcloud.Makefile create mode 100644 images.Makefile create mode 100644 redis-enterprise_manifest.yaml create mode 100644 redis-enterprise_sa_manifest.yaml rename Makefile => redis/Makefile (100%) rename README.md => redis/README.md (100%) rename {apptest => redis/apptest}/deployer/redis-operator/templates/tester.yaml (100%) rename {apptest => redis/apptest}/deployer/schema.yaml (100%) rename {apptest => redis/apptest}/tester/Dockerfile (100%) rename {apptest => redis/apptest}/tester/tester.sh (100%) rename {apptest => redis/apptest}/tester/tests/basic-suite.yaml (100%) rename {chart => redis/chart}/redis-operator/Chart.yaml (100%) rename {chart => redis/chart}/redis-operator/files/crd/redislabs.com_redisenterpriseclusters.app_crd.yaml (100%) rename {chart => redis/chart}/redis-operator/logo.png (100%) rename {chart => redis/chart}/redis-operator/templates/_helpers.tpl (100%) rename {chart => redis/chart}/redis-operator/templates/application.yaml (100%) rename {chart => redis/chart}/redis-operator/templates/configmap/crd.yaml (100%) rename {chart => redis/chart}/redis-operator/templates/deployment/operator.yaml (100%) rename {chart => redis/chart}/redis-operator/templates/job/crd-create.yaml (100%) rename {chart => redis/chart}/redis-operator/templates/rbac/rbac.yaml (100%) rename {chart => redis/chart}/redis-operator/templates/redis-enterprise-cluster/redis-enterprise-cluster.yaml (100%) rename {chart => redis/chart}/redis-operator/values.yaml (100%) rename {deployer => redis/deployer}/Dockerfile (100%) rename {resources => redis/resources}/service-accounts.yaml (100%) rename schema.yaml => redis/schema.yaml (100%) rename Dockerfile => tmp_Dockerfile (100%) create mode 100644 var.Makefile diff --git a/.gitignore b/.gitignore index e94f7ce..bc2a983 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .build/ .DS_Store +.idea/ \ No newline at end of file diff --git a/MARKETPLACE_TOOLS_TAG b/MARKETPLACE_TOOLS_TAG new file mode 100644 index 0000000..b0bb878 --- /dev/null +++ b/MARKETPLACE_TOOLS_TAG @@ -0,0 +1 @@ +0.9.5 diff --git a/app.Makefile b/app.Makefile new file mode 100644 index 0000000..a853bfb --- /dev/null +++ b/app.Makefile @@ -0,0 +1,116 @@ +ifndef __APP_MAKEFILE__ + +__APP_MAKEFILE__ := included + + +makefile_dir := $(dir $(realpath $(lastword $(MAKEFILE_LIST)))) +include $(makefile_dir)/common.Makefile +include $(makefile_dir)/var.Makefile + +VERIFY_WAIT_TIMEOUT = 600 + +##### Helper functions ##### + +# Extracts the name property from APP_PARAMETERS. +define name_parameter +$(shell echo '$(APP_PARAMETERS)' \ + | docker run -i --entrypoint=/bin/print_config.py --rm $(APP_DEPLOYER_IMAGE) --values_mode stdin --xtype NAME) +endef + + +# Extracts the namespace property from APP_PARAMETERS. +define namespace_parameter +$(shell echo '$(APP_PARAMETERS)' \ + | docker run -i --entrypoint=/bin/print_config.py --rm $(APP_DEPLOYER_IMAGE) --values_mode stdin --xtype NAMESPACE) +endef + + +##### Helper targets ##### + + +.build/app: | .build + mkdir -p "$@" + + +# (1) Always update the dev script to make sure it's up to date. +# There isn't currently a way to detect if the dev container has changed. +# (2) The mpdev script is first copied to the / tmp directory and +# then moved to the target path due to the "Text file busy" error. +.PHONY: .build/app/dev +.build/app/dev: .build/var/MARKETPLACE_TOOLS_TAG \ + | .build/app + @docker run \ + "gcr.io/cloud-marketplace-tools/k8s/dev:$(MARKETPLACE_TOOLS_TAG)" \ + cat /scripts/dev > "/tmp/dev" + @mv "/tmp/dev" "$@" + @chmod a+x "$@" + + +########### Main targets ########### + + +# Builds the application containers and push them to the registry. +# Including Makefile can extend this target. This target is +# a prerequisite for install. +.PHONY: app/build +app/build:: ; + + +# Installs the application into target namespace on the cluster. +.PHONY: app/install +app/install:: app/build \ + .build/var/APP_DEPLOYER_IMAGE \ + .build/var/APP_PARAMETERS \ + .build/var/MARKETPLACE_TOOLS_TAG \ + | .build/app/dev + $(call print_target) + .build/app/dev install \ + --deployer='$(APP_DEPLOYER_IMAGE)' \ + --parameters='$(APP_PARAMETERS)' \ + --entrypoint="/bin/deploy.sh" + + +# Installs the application into target namespace on the cluster. +.PHONY: app/install-test +app/install-test:: app/build \ + .build/var/APP_DEPLOYER_IMAGE \ + .build/var/APP_PARAMETERS \ + .build/var/MARKETPLACE_TOOLS_TAG \ + | .build/app/dev + $(call print_target) + .build/app/dev install \ + --deployer='$(APP_DEPLOYER_IMAGE)' \ + --parameters='$(APP_PARAMETERS)' \ + --entrypoint="/bin/deploy_with_tests.sh" + + +# Uninstalls the application from the target namespace on the cluster. +.PHONY: app/uninstall +app/uninstall: .build/var/APP_DEPLOYER_IMAGE \ + .build/var/APP_PARAMETERS + $(call print_target) + kubectl delete 'application/$(NAME)' \ + --namespace='$(NAMESPACE)' \ + --ignore-not-found + + +# Runs the verification pipeline. +.PHONY: app/verify +app/verify: app/build \ + .build/var/APP_DEPLOYER_IMAGE \ + .build/var/MARKETPLACE_TOOLS_TAG \ + | .build/app/dev + $(call print_target) + .build/app/dev verify \ + --deployer='$(APP_DEPLOYER_IMAGE)' \ + --wait_timeout="$(VERIFY_WAIT_TIMEOUT)" + + +# Runs diagnostic tool to make sure your environment is properly setup. +.PHONY: app/doctor +app/doctor: | .build/app/dev + $(call print_target) + .build/app/dev doctor + + +endif diff --git a/app_v2.Makefile b/app_v2.Makefile new file mode 100644 index 0000000..cb40dd9 --- /dev/null +++ b/app_v2.Makefile @@ -0,0 +1,146 @@ +# +# Used with deployer schema v2. +# + +ifndef __APP_V2_MAKEFILE__ + +__APP_V2_MAKEFILE__ := included + + +makefile_dir := $(dir $(realpath $(lastword $(MAKEFILE_LIST)))) +include $(makefile_dir)/common.Makefile +include $(makefile_dir)/var.Makefile + +VERIFY_WAIT_TIMEOUT = 600 + + +##### Validations and Information ##### + +ifndef APP_GCS_PATH +$(error APP_GCS_PATH must be defined) +endif + +$(info ---- APP_GCS_PATH = $(APP_GCS_PATH)) + +ifndef APP_DEPLOYER_IMAGE +$(error APP_DEPLOYER_IMAGE must be defined) +endif + +$(info ---- APP_DEPLOYER_IMAGE = $(APP_DEPLOYER_IMAGE)) + + +##### Helper functions ##### + +# Extracts the name property from APP_PARAMETERS. +define name_parameter +$(shell echo '$(APP_PARAMETERS)' \ + | docker run -i --entrypoint=/bin/print_config.py --rm $(APP_DEPLOYER_IMAGE) --values_mode stdin --xtype NAME) +endef + + +# Extracts the namespace property from APP_PARAMETERS. +define namespace_parameter +$(shell echo '$(APP_PARAMETERS)' \ + | docker run -i --entrypoint=/bin/print_config.py --rm $(APP_DEPLOYER_IMAGE) --values_mode stdin --xtype NAMESPACE) +endef + + +##### Helper targets ##### + + +.build/app: | .build + mkdir -p "$@" + + +# (1) Always update the dev script to make sure it's up to date. +# There isn't currently a way to detect if the dev container has changed. +# (2) The mpdev script is first copied to the / tmp directory and +# then moved to the target path due to the "Text file busy" error. +.PHONY: .build/app/dev +.build/app/dev: .build/var/MARKETPLACE_TOOLS_TAG \ + | .build/app + @docker run \ + "gcr.io/cloud-marketplace-tools/k8s/dev:$(MARKETPLACE_TOOLS_TAG)" \ + cat /scripts/dev > "/tmp/dev" + @mv "/tmp/dev" "$@" + @chmod a+x "$@" + + +########### Main targets ########### + + +# Builds the application containers and push them to the registry. +# Including Makefile can extend this target. This target is +# a prerequisite for install. +.PHONY: app/build +app/build:: ; + + +.PHONY: app/publish +app/publish:: app/build \ + .build/var/APP_DEPLOYER_IMAGE \ + .build/var/APP_GCS_PATH \ + .build/var/MARKETPLACE_TOOLS_TAG \ + | .build/app/dev + $(call print_target) + .build/app/dev publish \ + --deployer_image='$(APP_DEPLOYER_IMAGE)' \ + --gcs_repo='$(APP_GCS_PATH)' + + +# Installs the application into target namespace on the cluster. +.PHONY: app/install +app/install:: app/publish \ + .build/var/APP_DEPLOYER_IMAGE \ + .build/var/APP_PARAMETERS \ + .build/var/MARKETPLACE_TOOLS_TAG \ + | .build/app/dev + $(call print_target) + .build/app/dev install \ + --version_meta_file='$(APP_GCS_PATH)/$(RELEASE).yaml' \ + --parameters='$(APP_PARAMETERS)' + +# Installs the application into target namespace on the cluster. +.PHONY: app/install-test +app/install-test:: app/publish \ + .build/var/APP_DEPLOYER_IMAGE \ + .build/var/APP_PARAMETERS \ + .build/var/MARKETPLACE_TOOLS_TAG \ + | .build/app/dev + $(call print_target) + .build/app/dev install \ + --deployer='$(APP_DEPLOYER_IMAGE)' \ + --parameters='$(APP_PARAMETERS)' \ + --entrypoint="/bin/deploy_with_tests.sh" + + +# Uninstalls the application from the target namespace on the cluster. +.PHONY: app/uninstall +app/uninstall: .build/var/APP_DEPLOYER_IMAGE \ + .build/var/APP_PARAMETERS + $(call print_target) + kubectl delete 'application/$(NAME)' \ + --namespace='$(NAMESPACE)' \ + --ignore-not-found + + +# Runs the verification pipeline. +.PHONY: app/verify +app/verify: app/publish \ + .build/var/APP_DEPLOYER_IMAGE \ + .build/var/MARKETPLACE_TOOLS_TAG \ + | .build/app/dev + $(call print_target) + .build/app/dev verify \ + --deployer='$(APP_DEPLOYER_IMAGE)' \ + --wait_timeout="$(VERIFY_WAIT_TIMEOUT)" + + +# Runs diagnostic tool to make sure your environment is properly setup. +.PHONY: app/doctor +app/doctor: | .build/app/dev + $(call print_target) + .build/app/dev doctor + + +endif diff --git a/c2d_deployer.Makefile b/c2d_deployer.Makefile new file mode 100644 index 0000000..763c0c6 --- /dev/null +++ b/c2d_deployer.Makefile @@ -0,0 +1,137 @@ +# +# Used with app_v2 Makefile for deployer schema v2. +# + +ifndef __C2D_DEPLOYER_MAKEFILE__ + +__C2D_DEPLOYER_MAKEFILE__:= included + +##### Check required variables ##### + + +ifndef CHART_NAME +$(error CHART_NAME must be defined) +endif + +$(info ---- CHART_NAME = $(CHART_NAME)) + +ifndef APP_ID +$(error APP_ID must be defined) +endif + +$(info ---- APP_ID = $(APP_ID)) + +ifndef image-$(CHART_NAME) +$(error image-$(CHART_NAME) must be defined) +endif + +$(info ---- image-$(CHART_NAME) = $(image-$(CHART_NAME))) + + +##### Common variables ##### + +APP_DEPLOYER_IMAGE ?= $(REGISTRY)/$(APP_ID)/deployer:$(RELEASE) +APP_DEPLOYER_IMAGE_TRACK_TAG ?= $(REGISTRY)/$(APP_ID)/deployer:$(TRACK) +APP_TESTER_IMAGE ?= $(REGISTRY)/$(APP_ID)/tester:$(RELEASE) +APP_GCS_PATH ?= $(GCS_URL)/$(APP_ID)/$(TRACK) + + +include ../app_v2.Makefile + + +$(info ---- TRACK = $(TRACK)) +$(info ---- RELEASE = $(RELEASE)) + + +##### Helper targets ##### + +.build/$(CHART_NAME): | .build + mkdir -p "$@" + + +app/build:: .build/$(CHART_NAME)/VERSION \ + .build/$(CHART_NAME)/$(CHART_NAME) \ + .build/$(CHART_NAME)/images \ + .build/$(CHART_NAME)/deployer + + +.build/$(CHART_NAME)/deployer: deployer/* \ + chart/$(CHART_NAME)/* \ + chart/$(CHART_NAME)/templates/* \ + schema.yaml \ + .build/var/APP_DEPLOYER_IMAGE \ + .build/var/APP_DEPLOYER_IMAGE_TRACK_TAG \ + .build/var/MARKETPLACE_TOOLS_TAG \ + .build/var/REGISTRY \ + .build/var/TRACK \ + .build/var/RELEASE \ + | .build/$(CHART_NAME) + $(call print_target,$@) + docker build \ + --build-arg REGISTRY="$(REGISTRY)/$(APP_ID)" \ + --build-arg TAG="$(RELEASE)" \ + --build-arg CHART_NAME="$(CHART_NAME)" \ + --build-arg MARKETPLACE_TOOLS_TAG="$(MARKETPLACE_TOOLS_TAG)" \ + --tag "$(APP_DEPLOYER_IMAGE)" \ + -f deployer/Dockerfile \ + . + docker tag "$(APP_DEPLOYER_IMAGE)" "$(APP_DEPLOYER_IMAGE_TRACK_TAG)" + docker push "$(APP_DEPLOYER_IMAGE)" + docker push "$(APP_DEPLOYER_IMAGE_TRACK_TAG)" + @touch "$@" + + +.build/$(CHART_NAME)/$(CHART_NAME): .build/var/REGISTRY \ + .build/var/TRACK \ + .build/var/RELEASE \ + | .build/$(CHART_NAME) + $(call print_target,$@) + docker pull $(image-$(CHART_NAME)) + docker tag $(image-$(CHART_NAME)) "$(REGISTRY)/$(APP_ID):$(TRACK)" + docker tag $(image-$(CHART_NAME)) "$(REGISTRY)/$(APP_ID):$(RELEASE)" + docker push "$(REGISTRY)/$(APP_ID):$(TRACK)" + docker push "$(REGISTRY)/$(APP_ID):$(RELEASE)" + @touch "$@" + + +# map every element of ADDITIONAL_IMAGES list +# from [image-name] to .build/$(CHART_NAME)/[image-name] format +IMAGE_TARGETS_LIST = $(patsubst %,.build/$(CHART_NAME)/%,$(ADDITIONAL_IMAGES)) +.build/$(CHART_NAME)/images: $(IMAGE_TARGETS_LIST) + +# extract image name from rule with .build/$(CHART_NAME)/% +# and use % match as $* in recipe +$(IMAGE_TARGETS_LIST): .build/$(CHART_NAME)/%: .build/var/REGISTRY \ + .build/var/TRACK \ + .build/var/RELEASE \ + | .build/$(CHART_NAME) + $(call print_target,$*) + docker pull $(image-$*) + docker tag $(image-$*) "$(REGISTRY)/$(APP_ID)/$*:$(TRACK)" + docker tag $(image-$*) "$(REGISTRY)/$(APP_ID)/$*:$(RELEASE)" + docker push "$(REGISTRY)/$(APP_ID)/$*:$(TRACK)" + docker push "$(REGISTRY)/$(APP_ID)/$*:$(RELEASE)" + @touch "$@" + + +.build/$(CHART_NAME)/tester: .build/var/APP_TESTER_IMAGE \ + $(shell find apptest -type f) \ + | .build/$(CHART_NAME) + $(call print_target,$@) + cd apptest/tester \ + && docker build --tag "$(APP_TESTER_IMAGE)" . + docker push "$(APP_TESTER_IMAGE)" + @touch "$@" + + +########### Main targets ########### + + +.PHONY: .build/$(CHART_NAME)/VERSION +.build/$(CHART_NAME)/VERSION: + $(call print_target,$@) + @echo "$(C2D_CONTAINER_RELEASE)" | grep -qE "^$(TRACK).[0-9]+$$" || \ + ( echo "C2D_RELEASE doesn't start with TRACK or doesn't match TRACK exactly"; exit 1 ) + + +endif diff --git a/common.Makefile b/common.Makefile new file mode 100644 index 0000000..b8c1c8e --- /dev/null +++ b/common.Makefile @@ -0,0 +1,43 @@ +ifndef __COMMON_MAKEFILE__ + +__COMMON_MAKEFILE__ := included + + +define print_target + @$(call print_notice,Building $@...) +endef + + +define print_notice + printf "\n\033[93m\033[1m$(1)\033[0m\n" +endef + + +define print_error + printf "\n\033[93m\033[1m$(1)\033[0m\n" +endef + + +# MARKETPLACE_TOOLS_TAG is the tag of the container images published +# by marketplace-k8s-app-tools. +tag_from_file := $(shell cat "$(dir $(realpath $(lastword $(MAKEFILE_LIST))))/MARKETPLACE_TOOLS_TAG") +MARKETPLACE_TOOLS_TAG ?= $(tag_from_file) +export MARKETPLACE_TOOLS_TAG + +$(info ---- MARKETPLACE_TOOLS_TAG = $(MARKETPLACE_TOOLS_TAG)) + + +.build: + mkdir -p "$@" + + +.build/tmp: | .build + mkdir -p "$@" + + +.PHONY: clean +clean:: + rm -Rf .build + + +endif diff --git a/crd.Makefile b/crd.Makefile new file mode 100644 index 0000000..780966c --- /dev/null +++ b/crd.Makefile @@ -0,0 +1,20 @@ +ifndef __CRD_MAKEFILE__ + +__CRD_MAKEFILE__ := included + + +# Installs the application CRD on the cluster. +.PHONY: crd/install +crd/install: + kubectl apply -f "https://raw.githubusercontent.com/GoogleCloudPlatform/marketplace-k8s-app-tools/master/crd/app-crd.yaml" + kubectl apply -f "https://raw.githubusercontent.com/GoogleCloudPlatform/marketplace-k8s-app-tools/master/crd/kalm.yaml" + + +# Uninstalls the application CRD from the cluster. +.PHONY: crd/uninstall +crd/uninstall: + kubectl delete -f "https://raw.githubusercontent.com/GoogleCloudPlatform/marketplace-k8s-app-tools/master/crd/app-crd.yaml" + kubectl delete -f "https://raw.githubusercontent.com/GoogleCloudPlatform/marketplace-k8s-app-tools/master/crd/kalm.yaml" + + +endif diff --git a/gcloud.Makefile b/gcloud.Makefile new file mode 100644 index 0000000..920e28f --- /dev/null +++ b/gcloud.Makefile @@ -0,0 +1,38 @@ +ifndef __GCLOUD_MAKEFILE__ + +__GCLOUD_MAKEFILE__ := included + +# Include this Makefile to automatically derive registry +# and target namespace from the current gcloud and kubectl +# configurations. +# This is for convenience over having to specifying the +# environment variables manually. + + +ifndef REGISTRY + # We replace ':' with '/' characters to support a now-deprecated + # projects format "google.com:my-project". + REGISTRY := gcr.io/$(shell gcloud config get-value project | tr ':' '/') +endif + +ifndef GCS_URL + # We replace ':' with '_' characters to support a now-deprecated + # projects format "google.com:my-project". + # Note that a project does not have any bucket by default. + # This is just a conventional default value, where the user is expected + # to create GCS bucket with the same name as the project + GCS_URL := gs://$(shell gcloud config get-value project | tr ':' '_') +endif + +ifndef NAMESPACE + NAMESPACE := $(shell kubectl config view -o jsonpath="{.contexts[?(@.name==\"$$(kubectl config current-context)\")].context.namespace}") + ifeq ($(NAMESPACE),) + NAMESPACE = default + endif +endif + +$(info ---- REGISTRY = $(REGISTRY)) +$(info ---- GCS_URL = $(GCS_URL)) +$(info ---- NAMESPACE = $(NAMESPACE)) + +endif diff --git a/images.Makefile b/images.Makefile new file mode 100644 index 0000000..2cb4494 --- /dev/null +++ b/images.Makefile @@ -0,0 +1,10 @@ +# Gets first argv as image to resolve full path of image with sha256 hash sum. +get_sha256 = $(shell docker pull $1 2>&1 > /dev/null \ +&& docker inspect --format='{{ index .RepoDigests 0 }}' $1) + +# Gets first argv as image and second as variable to get from container. +get_var_from_container = $(shell docker pull $1 2>&1 > /dev/null \ +&& docker run --rm --entrypoint=printenv $1 $2) + +# Gets first argv as image and try to get C2D_RELEASE from container. +get_c2d_release = $(call get_var_from_container, $1, C2D_RELEASE) diff --git a/redis-enterprise_manifest.yaml b/redis-enterprise_manifest.yaml new file mode 100644 index 0000000..b91f254 --- /dev/null +++ b/redis-enterprise_manifest.yaml @@ -0,0 +1,255 @@ +--- +# Source: redis-operator/templates/configmap/crd.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: redis-enterprise-crd-config-map + labels: + app.kubernetes.io/name: redis-enterprise + app.kubernetes.io/component: crd-configmap +data: + redislabs.com_redisenterpriseclusters.app_crd.yaml: |- + apiVersion: apiextensions.k8s.io/v1beta1 + kind: CustomResourceDefinition + metadata: + name: redisenterpriseclusters.app.redislabs.com + spec: + group: app.redislabs.com + names: + kind: RedisEnterpriseCluster + listKind: RedisEnterpriseClusterList + plural: redisenterpriseclusters + singular: redisenterprisecluster + shortNames: + - rec + scope: Namespaced + version: v1 + versions: + - name: v1 + served: true + storage: true +--- +# Source: redis-operator/templates/rbac/rbac.yaml +kind: ServiceAccount +apiVersion: v1 +metadata: + name: redis-enterprise-redis-operator + namespace: default +--- +# Source: redis-operator/templates/rbac/rbac.yaml +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: redis-enterprise-operator + namespace: default +rules: +- apiGroups: ["", "extensions", "apps", "rbac.authorization.k8s.io", "policy"] + resources: ["*"] + verbs: ["*"] +- apiGroups: + - app.redislabs.com + resources: ["*"] + verbs: ["*"] +--- +# Source: redis-operator/templates/rbac/rbac.yaml +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: redis-enterprise-operator + namespace: default +subjects: +- kind: ServiceAccount + name: redis-enterprise-redis-operator + namespace: default +roleRef: + kind: Role + name: redis-enterprise-operator + apiGroup: rbac.authorization.k8s.io +--- +# Source: redis-operator/templates/deployment/operator.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis-enterprise-redis-operator + labels: + app.kubernetes.io/name: "redis-enterprise" + app.kubernetes.io/component: operator +spec: + replicas: 1 + selector: + matchLabels: + name: redis-enterprise-redis-operator + template: + metadata: + labels: + name: redis-enterprise-redis-operator + app.kubernetes.io/name: "redis-enterprise" + app.kubernetes.io/component: operator + spec: + serviceAccountName: redis-enterprise-redis-operator + initContainers: + - command: + - "/bin/bash" + - "-ec" + - | + timeout 120 bash -c ' + until kubectl get crd redisenterpriseclusters.app.redislabs.com; + do echo "Waiting for Redis CRDs created"; sleep 5; + done' + name: wait-for-crds-created + image: gcr.io/cloud-marketplace-tools/k8s/deployer_helm:0.8.0 + containers: + - name: redis-enterprise-operator + image: gcr.io/proven-reality-226706/redislabs/operator:1.15 + command: + - redis-enterprise-operator + imagePullPolicy: Always + env: + - name: WATCH_NAMESPACE + value: "" + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: OPERATOR_NAME + value: "redis-enterprise-redis-operator" +--- +# Source: redis-operator/templates/job/crd-create.yaml +apiVersion: batch/v1 +kind: Job +metadata: + annotations: + name: redis-enterprise-crd-job + labels: + app.kubernetes.io/name: "redis-enterprise" + app.kubernetes.io/component: crd-job +spec: + ttlSecondsAfterFinished: 300 + backoffLimit: 0 + completions: 1 + parallelism: 1 + template: + spec: + containers: + - command: + - "/bin/bash" + - "-ec" + - | + for crd in /crd_to_create/*; + do kubectl apply -f $crd; + done + image: gcr.io/cloud-marketplace-tools/k8s/deployer_helm:0.8.0 + imagePullPolicy: Always + name: crd-create + volumeMounts: + - name: crd-configmap + mountPath: /crd_to_create/ + dnsPolicy: ClusterFirst + restartPolicy: Never + serviceAccountName: redis-enterprise-crd-creator-job + volumes: + - name: crd-configmap + configMap: + name: redis-enterprise-crd-config-map +--- +# Source: redis-operator/templates/application.yaml +apiVersion: app.k8s.io/v1beta1 +kind: Application +metadata: + name: "redis-enterprise" + annotations: + kubernetes-engine.cloud.google.com/icon: >- + data:image/png;base64, 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 + marketplace.cloud.google.com/deploy-info: '{"partner_id": "redislabs-public", "product_id": "redis-operator", "partner_name": "Redis Labs"}' + labels: + app.kubernetes.io/name: "redis-enterprise" +spec: + descriptor: + type: Redis Enterprise Operator + version: "1.15" + description: |- + Redis Operator makes it easy to deploy and manage Redis Enterprise + on Kubernetes. + maintainers: + - name: Redis Labs + url: https://redislabs.com/company/contact/support/ + links: + - description: 'User Guide: Redis Enterprise' + url: https://support.redislabs.com + notes: |- + See more details and manual installation instructions here https://github.com/RedisLabs/gkemarketplace + selector: + matchLabels: + app.kubernetes.io/name: "redis-enterprise" + componentKinds: + - group: apps/v1 + kind: Deployment + - group: v1 + kind: ConfigMap + - group: v1 + kind: ServiceAccount + - group: v1 + kind: Job + - group: v1 + kind: Pod + - group: v1 + kind: Job + - group: v1 + kind: Service + - group: apps/v1 + kind: StatefulSet +--- +# Source: redis-operator/templates/redis-enterprise-cluster/redis-enterprise-cluster.yaml +apiVersion: "app.redislabs.com/v1" +kind: "RedisEnterpriseCluster" +metadata: + name: "redis-enterprise" + namespace: default + labels: + app.kubernetes.io/name: "redis-enterprise" +spec: + nodes: 3 + persistentSpec: + enabled: true + storageClassName: "standard" + uiServiceType: ClusterIP + #TODO uiServiceType: if INGRESS_AVAILABLE + username: admin@example.com + redisEnterpriseNodeResources: + limits: + cpu: "4000m" + memory: 15Gi + requests: + cpu: "4000m" + memory: 15Gi +# sideContainersSpec:#TODO +# - name: ubbagent +# image: "TODO" +# imagePullPolicy: IfNotPresent +# env: +# - name: NODE_CPU +# value: "4000m" +# - name: NODE_MEM +# value: 15Gi +# - name: AGENT_CONFIG_FILE +# value: /etc/ubbagent/config.yaml +# - name: AGENT_LOCAL_PORT +# value: "6080" +# - name: AGENT_STATE_DIR +# value: /opt/persistent/ubbagent +# - name: AGENT_REPORT_DIR +# value: /opt/persistent/ubbagent/reports +# - name: AGENT_ENCODED_KEY +# valueFrom: +# secretKeyRef: +# name: "redis-enterprise-reporting-secret" +# key: reporting-key +# - name: AGENT_CONSUMER_ID +# valueFrom: +# secretKeyRef: +# name: "redis-enterprise-reporting-secret" +# key: consumer-id diff --git a/redis-enterprise_sa_manifest.yaml b/redis-enterprise_sa_manifest.yaml new file mode 100644 index 0000000..62ea3b7 --- /dev/null +++ b/redis-enterprise_sa_manifest.yaml @@ -0,0 +1,136 @@ +# CRD creator service account +apiVersion: v1 +kind: ServiceAccount +metadata: + name: redis-enterprise-crd-creator-job +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: redis-enterprise-crd-creator-job-role +rules: +- apiGroups: + - apiextensions.k8s.io + resources: + - customresourcedefinitions + verbs: + - get + - list + - create +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: redis-enterprise-crd-creator-job-rb +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: redis-enterprise-crd-creator-job-role +subjects: +- kind: ServiceAccount + name: redis-enterprise-crd-creator-job + namespace: default +--- + +# Operator service account +apiVersion: v1 +kind: ServiceAccount +metadata: + name: redis-enterprise-redis-operator +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: redis-enterprise-redis-operator-role +rules: +- apiGroups: ["apiextensions.k8s.io"] + resources: ["customresourcedefinitions"] + verbs: ["get","list"] +- apiGroups: + - "" + resources: + - pods + - services + - endpoints + - persistentvolumeclaims + - events + - configmaps + - secrets + - serviceaccounts + verbs: + - '*' +- apiGroups: + - apps + resources: + - deployments + - daemonsets + - replicasets + - statefulsets + verbs: + - '*' +- apiGroups: + - monitoring.coreos.com + resources: + - servicemonitors + verbs: + - get + - create +- apiGroups: + - extensions + resources: + - replicasets + - deployments + - daemonsets + - statefulsets + - ingresses + verbs: + - '*' +- apiGroups: + - batch + resources: + - jobs + - cronjobs + verbs: + - '*' +#- apiGroups: +# - jaegertracing.io +# resources: +# - '*' +# verbs: +# - '*' +- apiGroups: + - rbac.authorization.k8s.io + resources: + - clusterrolebindings + verbs: + - '*' +- apiGroups: + - apps + - extensions + resourceNames: + - redis-operator + resources: + - deployments/finalizers + verbs: + - update +- apiGroups: + - kafka.strimzi.io + resources: + - kafkas + - kafkausers + verbs: + - '*' + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: redis-enterprise-redis-operator-rb +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: redis-enterprise-redis-operator-role +subjects: +- kind: ServiceAccount + name: redis-enterprise-redis-operator + namespace: default diff --git a/Makefile b/redis/Makefile similarity index 100% rename from Makefile rename to redis/Makefile diff --git a/README.md b/redis/README.md similarity index 100% rename from README.md rename to redis/README.md diff --git a/apptest/deployer/redis-operator/templates/tester.yaml b/redis/apptest/deployer/redis-operator/templates/tester.yaml similarity index 100% rename from apptest/deployer/redis-operator/templates/tester.yaml rename to redis/apptest/deployer/redis-operator/templates/tester.yaml diff --git a/apptest/deployer/schema.yaml b/redis/apptest/deployer/schema.yaml similarity index 100% rename from apptest/deployer/schema.yaml rename to redis/apptest/deployer/schema.yaml diff --git a/apptest/tester/Dockerfile b/redis/apptest/tester/Dockerfile similarity index 100% rename from apptest/tester/Dockerfile rename to redis/apptest/tester/Dockerfile diff --git a/apptest/tester/tester.sh b/redis/apptest/tester/tester.sh similarity index 100% rename from apptest/tester/tester.sh rename to redis/apptest/tester/tester.sh diff --git a/apptest/tester/tests/basic-suite.yaml b/redis/apptest/tester/tests/basic-suite.yaml similarity index 100% rename from apptest/tester/tests/basic-suite.yaml rename to redis/apptest/tester/tests/basic-suite.yaml diff --git a/chart/redis-operator/Chart.yaml b/redis/chart/redis-operator/Chart.yaml similarity index 100% rename from chart/redis-operator/Chart.yaml rename to redis/chart/redis-operator/Chart.yaml diff --git a/chart/redis-operator/files/crd/redislabs.com_redisenterpriseclusters.app_crd.yaml b/redis/chart/redis-operator/files/crd/redislabs.com_redisenterpriseclusters.app_crd.yaml similarity index 100% rename from chart/redis-operator/files/crd/redislabs.com_redisenterpriseclusters.app_crd.yaml rename to redis/chart/redis-operator/files/crd/redislabs.com_redisenterpriseclusters.app_crd.yaml diff --git a/chart/redis-operator/logo.png b/redis/chart/redis-operator/logo.png similarity index 100% rename from chart/redis-operator/logo.png rename to redis/chart/redis-operator/logo.png diff --git a/chart/redis-operator/templates/_helpers.tpl b/redis/chart/redis-operator/templates/_helpers.tpl similarity index 100% rename from chart/redis-operator/templates/_helpers.tpl rename to redis/chart/redis-operator/templates/_helpers.tpl diff --git a/chart/redis-operator/templates/application.yaml b/redis/chart/redis-operator/templates/application.yaml similarity index 100% rename from chart/redis-operator/templates/application.yaml rename to redis/chart/redis-operator/templates/application.yaml diff --git a/chart/redis-operator/templates/configmap/crd.yaml b/redis/chart/redis-operator/templates/configmap/crd.yaml similarity index 100% rename from chart/redis-operator/templates/configmap/crd.yaml rename to redis/chart/redis-operator/templates/configmap/crd.yaml diff --git a/chart/redis-operator/templates/deployment/operator.yaml b/redis/chart/redis-operator/templates/deployment/operator.yaml similarity index 100% rename from chart/redis-operator/templates/deployment/operator.yaml rename to redis/chart/redis-operator/templates/deployment/operator.yaml diff --git a/chart/redis-operator/templates/job/crd-create.yaml b/redis/chart/redis-operator/templates/job/crd-create.yaml similarity index 100% rename from chart/redis-operator/templates/job/crd-create.yaml rename to redis/chart/redis-operator/templates/job/crd-create.yaml diff --git a/chart/redis-operator/templates/rbac/rbac.yaml b/redis/chart/redis-operator/templates/rbac/rbac.yaml similarity index 100% rename from chart/redis-operator/templates/rbac/rbac.yaml rename to redis/chart/redis-operator/templates/rbac/rbac.yaml diff --git a/chart/redis-operator/templates/redis-enterprise-cluster/redis-enterprise-cluster.yaml b/redis/chart/redis-operator/templates/redis-enterprise-cluster/redis-enterprise-cluster.yaml similarity index 100% rename from chart/redis-operator/templates/redis-enterprise-cluster/redis-enterprise-cluster.yaml rename to redis/chart/redis-operator/templates/redis-enterprise-cluster/redis-enterprise-cluster.yaml diff --git a/chart/redis-operator/values.yaml b/redis/chart/redis-operator/values.yaml similarity index 100% rename from chart/redis-operator/values.yaml rename to redis/chart/redis-operator/values.yaml diff --git a/deployer/Dockerfile b/redis/deployer/Dockerfile similarity index 100% rename from deployer/Dockerfile rename to redis/deployer/Dockerfile diff --git a/resources/service-accounts.yaml b/redis/resources/service-accounts.yaml similarity index 100% rename from resources/service-accounts.yaml rename to redis/resources/service-accounts.yaml diff --git a/schema.yaml b/redis/schema.yaml similarity index 100% rename from schema.yaml rename to redis/schema.yaml diff --git a/Dockerfile b/tmp_Dockerfile similarity index 100% rename from Dockerfile rename to tmp_Dockerfile diff --git a/var.Makefile b/var.Makefile new file mode 100644 index 0000000..9149af6 --- /dev/null +++ b/var.Makefile @@ -0,0 +1,65 @@ +ifndef __VAR_MAKEFILE__ + +__VAR_MAKEFILE__ := included + +# Provides a class of targets that ensures variables are +# defined and trigger rebuilds when variable values change. +# +# Usage: +# +# my_target: .build/var/REGISTRY +# +# my_target rebuilds when the value of $(REGISTRY) changes. +# .build/var/REGISTRY also ensures that $(REGISTRY) value +# is non-empty. + + +# The main target that this Makefile offers. +# This is a real file that gets updated when a variable value +# change is detected. This rule does not have a recipe. It +# relies on the %-phony prerequisite to detect the change and +# update the file. +.build/var/%: .build/var/%-required .build/var/%-phony ; + + +.build/var: + mkdir -p .build/var + + +# Since we can't make pattern targets phony, we make them +# effectively phony by depending on this phony target. +.PHONY: var/phony +var/phony: ; + + +# An effectively phony target that always runs to compare the current +# value of the variable (say VARNAME) with the content of the +# corresponding .build/var/VARNAME file. If the contents differ, +# the recipe updates .build/var/VARNAME, which effectively trigger +# rebuilding of targets depending on .build/var.VARNAME. +.build/var/%-phony: var/phony | .build/var + @ \ + var_key="$*" ; \ + var_val="${$*}" ; \ + var_val_old=$$(cat ".build/var/$$var_key" 2> /dev/null) ; \ + if [ "$$var_val" != "$$var_val_old" ]; then \ + $(call print_notice,$$var_key has been updated) ; \ + printf " old value: $$var_val_old\n" ; \ + printf " new value: $$var_val\n" ; \ + printf "$$var_val" > .build/var/$$var_key ; \ + fi + + +# An effectively phony target that verifies that the variable +# has a non-empty value. +.build/var/%-required: var/phony + @ \ + var_key="$*" ; \ + var_val="${$*}" ; \ + if [ "$$var_val" = "" ]; then \ + $(call print_notice,Make variable '$*' is required); \ + exit 1; \ + fi + + +endif From c0c77acf3177a76ec4a9f9ed5a656101b462c43a Mon Sep 17 00:00:00 2001 From: Joshua Fox <4542591+JoshuaFox@users.noreply.github.com> Date: Sun, 26 Jan 2020 11:58:05 +0200 Subject: [PATCH 044/120] Helm --- redis-enterprise_manifest.yaml | 255 ------------------------------ redis-enterprise_sa_manifest.yaml | 136 ---------------- 2 files changed, 391 deletions(-) delete mode 100644 redis-enterprise_manifest.yaml delete mode 100644 redis-enterprise_sa_manifest.yaml diff --git a/redis-enterprise_manifest.yaml b/redis-enterprise_manifest.yaml deleted file mode 100644 index b91f254..0000000 --- a/redis-enterprise_manifest.yaml +++ /dev/null @@ -1,255 +0,0 @@ ---- -# Source: redis-operator/templates/configmap/crd.yaml -apiVersion: v1 -kind: ConfigMap -metadata: - name: redis-enterprise-crd-config-map - labels: - app.kubernetes.io/name: redis-enterprise - app.kubernetes.io/component: crd-configmap -data: - redislabs.com_redisenterpriseclusters.app_crd.yaml: |- - apiVersion: apiextensions.k8s.io/v1beta1 - kind: CustomResourceDefinition - metadata: - name: redisenterpriseclusters.app.redislabs.com - spec: - group: app.redislabs.com - names: - kind: RedisEnterpriseCluster - listKind: RedisEnterpriseClusterList - plural: redisenterpriseclusters - singular: redisenterprisecluster - shortNames: - - rec - scope: Namespaced - version: v1 - versions: - - name: v1 - served: true - storage: true ---- -# Source: redis-operator/templates/rbac/rbac.yaml -kind: ServiceAccount -apiVersion: v1 -metadata: - name: redis-enterprise-redis-operator - namespace: default ---- -# Source: redis-operator/templates/rbac/rbac.yaml -kind: Role -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: redis-enterprise-operator - namespace: default -rules: -- apiGroups: ["", "extensions", "apps", "rbac.authorization.k8s.io", "policy"] - resources: ["*"] - verbs: ["*"] -- apiGroups: - - app.redislabs.com - resources: ["*"] - verbs: ["*"] ---- -# Source: redis-operator/templates/rbac/rbac.yaml -kind: RoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: redis-enterprise-operator - namespace: default -subjects: -- kind: ServiceAccount - name: redis-enterprise-redis-operator - namespace: default -roleRef: - kind: Role - name: redis-enterprise-operator - apiGroup: rbac.authorization.k8s.io ---- -# Source: redis-operator/templates/deployment/operator.yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: redis-enterprise-redis-operator - labels: - app.kubernetes.io/name: "redis-enterprise" - app.kubernetes.io/component: operator -spec: - replicas: 1 - selector: - matchLabels: - name: redis-enterprise-redis-operator - template: - metadata: - labels: - name: redis-enterprise-redis-operator - app.kubernetes.io/name: "redis-enterprise" - app.kubernetes.io/component: operator - spec: - serviceAccountName: redis-enterprise-redis-operator - initContainers: - - command: - - "/bin/bash" - - "-ec" - - | - timeout 120 bash -c ' - until kubectl get crd redisenterpriseclusters.app.redislabs.com; - do echo "Waiting for Redis CRDs created"; sleep 5; - done' - name: wait-for-crds-created - image: gcr.io/cloud-marketplace-tools/k8s/deployer_helm:0.8.0 - containers: - - name: redis-enterprise-operator - image: gcr.io/proven-reality-226706/redislabs/operator:1.15 - command: - - redis-enterprise-operator - imagePullPolicy: Always - env: - - name: WATCH_NAMESPACE - value: "" - - name: POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: POD_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: OPERATOR_NAME - value: "redis-enterprise-redis-operator" ---- -# Source: redis-operator/templates/job/crd-create.yaml -apiVersion: batch/v1 -kind: Job -metadata: - annotations: - name: redis-enterprise-crd-job - labels: - app.kubernetes.io/name: "redis-enterprise" - app.kubernetes.io/component: crd-job -spec: - ttlSecondsAfterFinished: 300 - backoffLimit: 0 - completions: 1 - parallelism: 1 - template: - spec: - containers: - - command: - - "/bin/bash" - - "-ec" - - | - for crd in /crd_to_create/*; - do kubectl apply -f $crd; - done - image: gcr.io/cloud-marketplace-tools/k8s/deployer_helm:0.8.0 - imagePullPolicy: Always - name: crd-create - volumeMounts: - - name: crd-configmap - mountPath: /crd_to_create/ - dnsPolicy: ClusterFirst - restartPolicy: Never - serviceAccountName: redis-enterprise-crd-creator-job - volumes: - - name: crd-configmap - configMap: - name: redis-enterprise-crd-config-map ---- -# Source: redis-operator/templates/application.yaml -apiVersion: app.k8s.io/v1beta1 -kind: Application -metadata: - name: "redis-enterprise" - annotations: - kubernetes-engine.cloud.google.com/icon: >- - data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAIAAAABc2X6AAAAA3NCSVQICAjb4U/gAAASrElEQVR4nO2beZRdRZ3Hv7+quvft/d7rTqe70+lOpzshgSQsQgAJoM4gbpyZo45nxEEYxA3UKEo4wjkyhhkcFXBwO/4BbsMwHnXwMDMgI4wDCIGoYVFjgoHe13R3enn7vbeqfvPH6+50p9/rLIZ4Tszv9F/31a36fepXv6XqVhMz489JxJ9agRMtp4BPdjkFfLLLKeCTXU4Bn+xyCvhklz87YHWCx/P7egu/2lnc9ay/ex8znPpU6KKL4xddGt646cQoQCdmt8SBn3vqianv3Fv6wYPUCJGuo1AYABuD3IjthnPZG5LXX1/zlneIWPw11eQ1B/Ze+cP0f/xo+tbbKA3RkKRIAtLF/EGJwAwT2NEBM4Saz302deVV4dM3vEb6vFbAtlCY/PEPM9+80/x6rzgtRjXLWQehc99oRsb8Z35Gda6obQaDC9M8PEH1dVSTBBhEnD1gu6dF+6qardtqr7lWRKPHV7HjD5x7bkf2wR/l7/kaNUHUt0KqWXuy7elpHyz6g4PZRx6e2vopRBC/8bPxK64Ir1nbt2U11SwHCACIYA0f6LW9iH5ma81fvzN+yRuPl3rHDViPjmQef2xq2/U2VxAtdRRJzGg/Mw4BZHo74x++pfGfvgAAbEEzOWL0y1/M3ft5SjVjkTJczNjhAyii7vsPJN50mapf/kfqeRyAC799cfIbXyvc+z2xEqKhDSQO0ZsDz746ZApIfmZr8prrIpvOPKSH/ndeUXjoEbUCYnkL5OLEQYC1Iz12ENGPXpf+yA3Rs193zNoeO7CZmhj/+tcyt20nQGxqoFBssX0AwC85m9+YvuHG2Lmbl+yPs089OfmVf9a9e0k5VRt5ebt7lIHU3XfWXvsBma49WrWPGpiNzj7xxPS93/Ae+y+qj4lkw8wP1sDqGWYiCAmhAMBaCME+kRsViaSsjcrWte6mc+quvQ6ALRWLv/1NMNRvxqZsZiro3us99wRoxheo/EdQRIFlO/OUAPBEl80i/JfvSt7wicTFl855x/EE9np7co/9z9TnPsl5X7avgBMCEftFu38Y+yE3tcnWdRSOEMEGvh3s1rv2IgrZsRxuDMxgC7awzDYIdo+umTwgU7WTD9w/dtXVsgUUjkCFyInCCZVRJaGoedy33Tl/3PfPr422Rh0LMgc1ZgSe+e2w3NBe86mbE5e/xW1tOz7Aued2TPzDLf7Op6kOom7GSxlsn+8JXfXemve8J/GGvxDJ1CFv2WKx8MudmZ88WPj6N8XG5RSKHfxNl2THOa0PPtKZIrG2DXwwvEkCM+W07S+Y6cBIUMmaneMZMuQ64s1NiTUJN6aEb+YZ3Bqe6LXjcDZvqbvji/HXX3yMwGZqcuzOL+Xv+xJPQG5cCTnPtQhmV3fbyJBqaDrsfJmpycFrrzTdv4MKzb1v93dF3nN96aHvUl0TGAQoQsnyQMEc8IxmKKLyks4b8+sD2ZAUDHgBB5o7akMX1kY74q5nF2pvArN7gBrCrTv3OStbjg64+PyugfM2y3UJii+bKYbmvWX7uhofeiZ20ZbD0s5oMjnZVVurzms/2A+RLWRFNAFmAqZ821/QBc2SSMxPZ/OA5x6WAs4U9XmNsbetSAiiBSMRiaku+uidrZ+5qaImVTcPww/92F2fsrE0gEXhlxGGP9ATw5EC6wNjdMgjZhGJl3tmIO6IFVE5VjJTPivgUAwAgGWUtHUEXbA8ekZNuCGs/IUWJkCAuzJwQ+HWKppUBe4TIT1VWBNLS4JetAhEQ/v4316l0rWJy95y2AiZf+7Z4b/aIs9dXTlvASCSxHUhVe+qgG1fXk8GHBgoMVO7+JqNtc0x59ymxMZkxDJrsGcP9iYJYEwGdvekXxrFoYn+SIAdUOe4P6y85phck1AEGMbBEZjV5vb973r7+JrW8OXvjJx/aezi16v6BhICALM1k5OF558v7niq+H8/NS++JNe1zbEBgDUIfDYeezl4vh2A6KgVqaQBBFFHwrGMbGD7CiYT2MBiS0P8delIylEa1rP24EQBAhBEfYWgL2cKhl1BcsnZX2I/zEqBCH153ZfXjWHZElNJR+g5p7cs16+GscWHHyj84Ktj/QBANYACJsAANUDU1VIoJtaugvbY97g4YXtAgNy4TF3wZqexxV2z3mmoj5x/wdjdXy7+9/0UigIwDAAJR2xKCc/AFRBEhtnjBaiSUDIYKOj+vDHMSpArKjjCkQPPtiACMFqyQ8XSspBsiallITmTDBkQgiIJiiREAwGz3t4+M7Dp7aKxCbFlk9OyTm08y13Z5J6+0W1sVHV18/e9hZdeyH/rLtmxesF8A4ahBCww31XL8TyruS+vR4qGGUpQRZ8/RuCyCIJLYjrg8QlfEdbVuMtC5Eo6uM7n+yczAJsZSX/+q7Uf+RjJpVcZht96rmxfdVgdJIGBrM97MkHWt44gSYRDSIkYMEFQFaTaDyYIbAlYOHMEuIIE0Z5p/+kx7zeTQcmwosoTLJJNU1/4ZPfq+PAtN/n9fdUGGr75RsTdJSIfAZJIEnXn9LOj3q/GPc9wSFKF9cvs9w2qVa3nXX991d6q5eGgUPjPt79t6qlfhFtWVHuZAd9wyhXNUdkYkYJg5wc2lMsgy17O/n489L6/SV33kfgll5Ljzv2ee/YXI1veIOfn50NRkdN2IG8HixoMVd1LjTEUipy5/faz3//+am2WAi7L3kce2fX+K00kJmXVoRjQlgWoLSFWRJywXBjPZ8Yh+AXdPSKam9O33pF8+ztk3TLre93JsNiwavFCI0AQpn3uzgVjJeuISvacFc1MwIoQ1q5qqfv4zbHL3ypisWqNlwL2e7unv3Ov9/C/9ZeoN2fyxjq01MCGEVhuiMjVMZl0hQUdUvnNHWXoXqTvuiPo7Sv9/McUSc5vIomY+YBn9mV1LrChJSc6MOwKaonJtrhDBGONHe21A1jV+arb3nF0wLn/fXz4zZfL09MUTQmCAHKB7cqZ/SUjiWR1bAtowxFJzTHZFlcA2cVjEHFmHMqhSM3MA0AAltGZ04MFbRiqeuS1DMMcU3RajVMXEjybyco9y8yQ/uA/tm87ytKy/8mfx09Pm2iKAcuwQESJs9KiYNRw0fTntW/hVNJJAK4kA3RldU/ONEZkS0zWKBHMx2amRN0cqiLKaduX18MFA0BWX8CG2TdojcsVEZV2SWNBFSgAa/n3I6VkONxehasq8H7pvjKaOaM1GRKky0oCAcMV1BZXa+LOUFF353Res6TKpW85wIwUTV9OLwuL9oRKuYIZjBkPL1vVs/xyVg8UjCtQff1CW1aCVsZUe1xJkAUH81DLT4aLZu90wBksq74AlygtMZ41v9jvtcZlR8JxBQJLDGaAGT54eUQ2RVQmMF05PVgwIUEVQ6gghBVlNe8a92MOtcVUOiRcQdpiOjCDBTPmWYcoXCVVW3BgoCStS6rmqALIMJvZmFjOWAz05oLOnAbDmTXPUQMDkAJS0lDBdmaKyyKyPa7qQ8LMhiLLsOCoEmelQ2enMVDQnVntGVayQkolwJXkW+zNBNrCMARBESmBUBWrGoZhrg/LjrhKh4Rv2MwUdwdRi4b3ZbyhvHEVqcVFyNECl0UQYo4oaH5pwg9LsSomm6JSzqZcBjQzgKaIaorIqcD25/X+olFCLA5sZXdVh6m7oC0DaInJlqiKKGGYSweDUnm3gLGS7ckHGR+CEHUWzXD11LMk8MKNuCNIM+/LBi9PB20J1RiWSedgKLJgAElH1KVDxRruyQf7iyawcI6goJ9REtCWw0KsqpGtMSUYZnY2Z3QlaMv7PftyRmvLjiBVYS2RxaIq4IiASZgi5MKzjrKJINGfN71ZnQyJNQmVdgUzzKzSAbMjsC7hrE84/QXdmzdFw6pKYJtDDQxHFK1LOk1RaXkmL8wNKok8a3typienATjVNkZEpb7BxHnnnv+JT1TFWqLwePjv3jf47z+ItjZTlTYMBJZdQU0RsSbhiEVHBYIgiaZ905PXQ3m7uAC2jIA55Yh1SZVypAHbBWmGFOGAr7uyZsKzUkBWmbVyKjYDQ296/PH2yy6rRnQYYADd//q956+5NtvcJJesscpeVx+WLTGZdsV8+2DWRCVje/J6tGQ9wwQww5WUdqklJlOunF+El+tKyxgp2t58UNC8RBFSdoSoEi0xsSJEBET//lP1W288amA2euhDH/AeuN89e82BUtCbM+Mlo8SSK5MRMMelaEuohrCghXuJMgYxMppLhl3BNY4SxGZhG0nwLIYLujunDS8VApgRWE661BpXjWFVzhpgti/0pL71zbqP3lDxrao+PPqF7d5j99OZ7YExSUecUyt9y69mglHPlgPG4leI4BJ5zHum/D1AW1w1R2V4ds88VwDGFMUcAsPOW8AElkRFzT15M5DXUtASa6ocydKu6Eg4SVda5rnYJoUI2t0u39RV4aoK/IqmunAyDjazuUcQzkg5p1kc8O2+ab9o4FRJueUKpDdnunM67cqOhEy7Qs9acl42nRFX0KTPr2T8Kd8KIrdKpc6AYQ4M1idVY2RmKvXsnElAEv0h63d3+Zuqr4uqwATs6JleKRKrYyqqZqykGSDUh0RDQziruSsbTHiWuXJJKAUkKKvtrw+YqKRVCdkUVpJmC32CYIBo0jOduWDKY0dStQVcjo4JR7REVWOkfEQ5EyBnYrjhvqLuy2kDSCxVgCyVhx0Ho0UzVDBNEdUWk4nZEzwLWEZU0lnpkGe5P68HCqZc6y4eqXxIEjBentKdwjRHZcoRIUmBtZkAw0Wd0+yIqla1DM2cckV73KkLScts55XiksgztjMf9OcNAVKQAo69tAQgiFzCWMkMFnTCoXU1TtoVRJg1OEtCR8I5rcYZLJq+nM4GtpyKFmOXrdefNz0cWCYilL202lGjtkxE9WGxtsaJCgpw0FEFgYBcYHvydjAfhKRY4iTkyIEPdiEIYUm+xQsTfkhSQ0isqXEkyIDLfmUYDWHRFHbz2g4UbH9OE0FW3DwSxJIH9wxow0LQ+qSzPCwdAcPw52ISQRD153V/weQCVoRIhWoLtvohXlVg63uHuEJ5cTJjsGj6CqY+LFfFRMqRc4vcAmEp1tWIjrgaKpqhgslqW3HPXA01sJxyxaqkUx8S5W7nO2rR2N6iGcwbz7Ajya04b4HvASu3XFRtlKp5uOeZZ352ySXRhmUUClWsxcsnGwmHWuNOOevyvIwqQIJ43LPdOT3tl/fMVVHLnplyREdCpd0F9VY5exc09+SC4aItfxyvxEEw2h/cH96w4dL77lt54YVHDQxg5IUXnvr4DdPP/TK8LCUila8PlQsdZnTUqBURFZHQCwsJR1AusJ25YH/RzmWsOTHM2qIuTOtr3LhD2s6fMgjCAZ97ssF4yboVz2XLOgS+NzLupGvOvPMr51x3XTWcwwOXZWzPnqdv3jbxyE8VoKof2RqGZU441B536sMz4XQOWwIGGC6azmygGWAiAgMtMdkalWEp5r7rl00Kpp580JfTwRKHW0R2atLPFOKbN1/0lX9pvfiIPmUe6ZWHia7OPzz4k90336wA1VRPyq24CSv7YUSKlVGxIipdQfMrRwmAUDIcWJaCooJAB4/4yluxvLF9eTNcMJa5auy11oyPlDys/uAHz9q6tWHTUdzTPOpLLS99+9t7v3pP/ne7Q8trKRyp6N48e2TbGpOtMRWTC/aoNK/Z7BMWRLnAduf0cMG4UlQmJYK1/sCwcNF+063nffrT0bpqFWRVOcZrS/07duzavn3g8ccTgFrZBHHo3ayylHd/aVd0xNXysPQsDv0yAbiCMoHdOx1MeNUP3Im4kC+NTzFw9j33nL91K4409i/q6Y+5mOZNTv7m+9//3Y03AnAbl5FbOZ4z4Fsm4LQa1RxVikAEMBnmsZLtzgVTPocrB18AbIaGSwaNV1xx/vbtTa879itpZTkON/F0sdj79NMv3L59csezoZiU6eXVpl8zCyAshSsQWJSs1bZqTGJrzeBIAKzbvn3jlVfWrl37R+pZluN5uXTkxRf3PvBA5913S8BpboSU1Qw+M3ZljYh9zxsZD4VDp9911zkf+rB03YoNj02O/23aIJ9/4b779m67yQTaWZYS0SpXEivoQjYz7U3lEhdccOa2bWe8+93HV7GZQV67C+KDO3f+6vbb9z/6aMSFXN60dJgxo0NFDyuvfO8Ft9xafzRp5mjlNb8RP7Fv3+9/+MPO226zgNvcCHFoBWzGhoIS1t5221lXX53qqPzJ7zjKCfqfB10svvjd7+7Z+rHAwIk7pBwYrbO+AVZv3XrhLbfEGxtPgBo4YcBz8uqjjw48+WSpUHDD4WUbNpx5zdVHfhH2uMiJBv6Ty5/dP2qdAj7Z5RTwyS6ngE92OQV8sssp4JNd/h+1JIvfRqdz6QAAAABJRU5ErkJggg== - marketplace.cloud.google.com/deploy-info: '{"partner_id": "redislabs-public", "product_id": "redis-operator", "partner_name": "Redis Labs"}' - labels: - app.kubernetes.io/name: "redis-enterprise" -spec: - descriptor: - type: Redis Enterprise Operator - version: "1.15" - description: |- - Redis Operator makes it easy to deploy and manage Redis Enterprise - on Kubernetes. - maintainers: - - name: Redis Labs - url: https://redislabs.com/company/contact/support/ - links: - - description: 'User Guide: Redis Enterprise' - url: https://support.redislabs.com - notes: |- - See more details and manual installation instructions here https://github.com/RedisLabs/gkemarketplace - selector: - matchLabels: - app.kubernetes.io/name: "redis-enterprise" - componentKinds: - - group: apps/v1 - kind: Deployment - - group: v1 - kind: ConfigMap - - group: v1 - kind: ServiceAccount - - group: v1 - kind: Job - - group: v1 - kind: Pod - - group: v1 - kind: Job - - group: v1 - kind: Service - - group: apps/v1 - kind: StatefulSet ---- -# Source: redis-operator/templates/redis-enterprise-cluster/redis-enterprise-cluster.yaml -apiVersion: "app.redislabs.com/v1" -kind: "RedisEnterpriseCluster" -metadata: - name: "redis-enterprise" - namespace: default - labels: - app.kubernetes.io/name: "redis-enterprise" -spec: - nodes: 3 - persistentSpec: - enabled: true - storageClassName: "standard" - uiServiceType: ClusterIP - #TODO uiServiceType: if INGRESS_AVAILABLE - username: admin@example.com - redisEnterpriseNodeResources: - limits: - cpu: "4000m" - memory: 15Gi - requests: - cpu: "4000m" - memory: 15Gi -# sideContainersSpec:#TODO -# - name: ubbagent -# image: "TODO" -# imagePullPolicy: IfNotPresent -# env: -# - name: NODE_CPU -# value: "4000m" -# - name: NODE_MEM -# value: 15Gi -# - name: AGENT_CONFIG_FILE -# value: /etc/ubbagent/config.yaml -# - name: AGENT_LOCAL_PORT -# value: "6080" -# - name: AGENT_STATE_DIR -# value: /opt/persistent/ubbagent -# - name: AGENT_REPORT_DIR -# value: /opt/persistent/ubbagent/reports -# - name: AGENT_ENCODED_KEY -# valueFrom: -# secretKeyRef: -# name: "redis-enterprise-reporting-secret" -# key: reporting-key -# - name: AGENT_CONSUMER_ID -# valueFrom: -# secretKeyRef: -# name: "redis-enterprise-reporting-secret" -# key: consumer-id diff --git a/redis-enterprise_sa_manifest.yaml b/redis-enterprise_sa_manifest.yaml deleted file mode 100644 index 62ea3b7..0000000 --- a/redis-enterprise_sa_manifest.yaml +++ /dev/null @@ -1,136 +0,0 @@ -# CRD creator service account -apiVersion: v1 -kind: ServiceAccount -metadata: - name: redis-enterprise-crd-creator-job ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: redis-enterprise-crd-creator-job-role -rules: -- apiGroups: - - apiextensions.k8s.io - resources: - - customresourcedefinitions - verbs: - - get - - list - - create ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: redis-enterprise-crd-creator-job-rb -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: redis-enterprise-crd-creator-job-role -subjects: -- kind: ServiceAccount - name: redis-enterprise-crd-creator-job - namespace: default ---- - -# Operator service account -apiVersion: v1 -kind: ServiceAccount -metadata: - name: redis-enterprise-redis-operator ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: redis-enterprise-redis-operator-role -rules: -- apiGroups: ["apiextensions.k8s.io"] - resources: ["customresourcedefinitions"] - verbs: ["get","list"] -- apiGroups: - - "" - resources: - - pods - - services - - endpoints - - persistentvolumeclaims - - events - - configmaps - - secrets - - serviceaccounts - verbs: - - '*' -- apiGroups: - - apps - resources: - - deployments - - daemonsets - - replicasets - - statefulsets - verbs: - - '*' -- apiGroups: - - monitoring.coreos.com - resources: - - servicemonitors - verbs: - - get - - create -- apiGroups: - - extensions - resources: - - replicasets - - deployments - - daemonsets - - statefulsets - - ingresses - verbs: - - '*' -- apiGroups: - - batch - resources: - - jobs - - cronjobs - verbs: - - '*' -#- apiGroups: -# - jaegertracing.io -# resources: -# - '*' -# verbs: -# - '*' -- apiGroups: - - rbac.authorization.k8s.io - resources: - - clusterrolebindings - verbs: - - '*' -- apiGroups: - - apps - - extensions - resourceNames: - - redis-operator - resources: - - deployments/finalizers - verbs: - - update -- apiGroups: - - kafka.strimzi.io - resources: - - kafkas - - kafkausers - verbs: - - '*' - ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: redis-enterprise-redis-operator-rb -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: redis-enterprise-redis-operator-role -subjects: -- kind: ServiceAccount - name: redis-enterprise-redis-operator - namespace: default From 623ae2a6b3d0e562e934da25ee1c75341e87f23e Mon Sep 17 00:00:00 2001 From: Joshua Fox <4542591+JoshuaFox@users.noreply.github.com> Date: Sun, 26 Jan 2020 16:56:26 +0200 Subject: [PATCH 045/120] Helm --- redis/apptest/tester/tests/basic-suite.yaml | 4 ++-- ...edislabs.com_redisenterpriseclusters.app_crd.yaml | 7 +------ redis/chart/redis-operator/templates/_helpers.tpl | 7 +++++-- .../redis-enterprise-cluster.yaml | 2 +- redis/resources/service-accounts.yaml | 12 ++++-------- 5 files changed, 13 insertions(+), 19 deletions(-) diff --git a/redis/apptest/tester/tests/basic-suite.yaml b/redis/apptest/tester/tests/basic-suite.yaml index 31a7a25..303b51d 100644 --- a/redis/apptest/tester/tests/basic-suite.yaml +++ b/redis/apptest/tester/tests/basic-suite.yaml @@ -19,7 +19,7 @@ actions: bashTest: script: | kubectl apply --namespace ${NAMESPACE} -f - < Date: Sun, 26 Jan 2020 17:21:07 +0200 Subject: [PATCH 046/120] HELM --- redis/apptest/tester/tests/basic-suite.yaml | 2 +- redis/chart/redis-operator/templates/application.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/redis/apptest/tester/tests/basic-suite.yaml b/redis/apptest/tester/tests/basic-suite.yaml index 303b51d..0f85202 100644 --- a/redis/apptest/tester/tests/basic-suite.yaml +++ b/redis/apptest/tester/tests/basic-suite.yaml @@ -1,4 +1,4 @@ -actions: +actions: #TODO test and write new tests - name: kubectl smoke test bashTest: script: kubectl version diff --git a/redis/chart/redis-operator/templates/application.yaml b/redis/chart/redis-operator/templates/application.yaml index 45750e1..b9a916a 100644 --- a/redis/chart/redis-operator/templates/application.yaml +++ b/redis/chart/redis-operator/templates/application.yaml @@ -1,5 +1,5 @@ apiVersion: app.k8s.io/v1beta1 -kind: Application +kind: Application #TODO The CRD is supposed to pause before creating this application, but the sleep process is concurrent with applying this K8s object, so we find that the CRD is not yet available metadata: name: "{{ .Release.Name }}" annotations: From 0c00c9de8a8a8d5ae82b04bdb69e0a50e5d775bd Mon Sep 17 00:00:00 2001 From: Joshua Fox <4542591+JoshuaFox@users.noreply.github.com> Date: Mon, 27 Jan 2020 11:31:19 +0200 Subject: [PATCH 047/120] Helm --- redis/chart/redis-operator/templates/application.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/redis/chart/redis-operator/templates/application.yaml b/redis/chart/redis-operator/templates/application.yaml index b9a916a..f40e113 100644 --- a/redis/chart/redis-operator/templates/application.yaml +++ b/redis/chart/redis-operator/templates/application.yaml @@ -1,5 +1,5 @@ apiVersion: app.k8s.io/v1beta1 -kind: Application #TODO The CRD is supposed to pause before creating this application, but the sleep process is concurrent with applying this K8s object, so we find that the CRD is not yet available +kind: Application # Question: The CRD creation is supposed to pause before creating the application, but in fact the CRD creation is launched concurrently with applying this K8s object, so we find that the CRD is not yet available unless we run everything twice! metadata: name: "{{ .Release.Name }}" annotations: From 1abbe5492669256e60535783644f26a742247167 Mon Sep 17 00:00:00 2001 From: Joshua Fox <4542591+JoshuaFox@users.noreply.github.com> Date: Mon, 27 Jan 2020 11:31:37 +0200 Subject: [PATCH 048/120] Helm --- redis/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/redis/Makefile b/redis/Makefile index c15d36d..6d2993e 100644 --- a/redis/Makefile +++ b/redis/Makefile @@ -30,7 +30,7 @@ image-deployer-helm ?= $(call get_sha256,$(IMAGE_DEPLOYER_HELM)) C2D_CONTAINER_RELEASE := $(call get_c2d_release,$(image-$(CHART_NAME))) -BUILD_ID := $(shell date --utc +%Y%m%d-%H%M%S) +BUILD_ID := $(shell date -u +%Y%m%d-%H%M%S) RELEASE ?= $(C2D_CONTAINER_RELEASE)-$(BUILD_ID) $(info ---- TRACK = $(TRACK)) From f532174f28ae89dc7e9947e0e584891bb2fd0d89 Mon Sep 17 00:00:00 2001 From: Joshua Fox <4542591+JoshuaFox@users.noreply.github.com> Date: Mon, 27 Jan 2020 11:42:05 +0200 Subject: [PATCH 049/120] Helm --- redis/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/redis/Makefile b/redis/Makefile index 6d2993e..48ac16f 100644 --- a/redis/Makefile +++ b/redis/Makefile @@ -50,7 +50,7 @@ APP_PARAMETERS ?= { \ # app_v2.Makefile provides the main targets for installing the application. # It requires several APP_* variables defined above, and thus must be included after. -include ../ +include ../c2d_deployer.Makefile # Build tester image app/build:: .build/$(CHART_NAME)/tester From 9b94d942137937d8b636d24b71d9b79bc3ad5d7c Mon Sep 17 00:00:00 2001 From: Joshua Fox <4542591+JoshuaFox@users.noreply.github.com> Date: Tue, 28 Jan 2020 17:06:48 +0200 Subject: [PATCH 050/120] Helm --- redis/schema.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/redis/schema.yaml b/redis/schema.yaml index 8f7f17b..d337d2e 100644 --- a/redis/schema.yaml +++ b/redis/schema.yaml @@ -106,6 +106,5 @@ required: - namespace - operator.redisAdmin - operator.replicas -- operator.reportingSecret - operator.nodeCpu - operator.nodeMem \ No newline at end of file From fc5d1b422a17dee9f7f6185ab09a6eaa400e7ad8 Mon Sep 17 00:00:00 2001 From: Joshua Fox <4542591+JoshuaFox@users.noreply.github.com> Date: Sun, 2 Feb 2020 16:49:43 +0200 Subject: [PATCH 051/120] ubbagent; ingressAvailable --- redis/Makefile | 10 +-- redis/README.md | 2 +- redis/chart/redis-operator/Chart.yaml | 3 +- .../redis-enterprise-cluster.yaml | 61 +++++++++---------- redis/chart/redis-operator/values.yaml | 3 +- redis/schema.yaml | 23 +++---- tmp_Dockerfile | 1 - 7 files changed, 53 insertions(+), 50 deletions(-) delete mode 100644 tmp_Dockerfile diff --git a/redis/Makefile b/redis/Makefile index 48ac16f..66466d6 100644 --- a/redis/Makefile +++ b/redis/Makefile @@ -9,12 +9,12 @@ CHART_NAME := redis-operator APP_ID ?= $(CHART_NAME) #SOURCE_REGISTRY ?= marketplace.gcr.io/google - +PROJECT=joshua-playground SOURCE_REGISTRY ?= gcr.io -TRACK ?= 1.15 +TRACK ?= 1.18 -IMAGE_MAIN ?= $(SOURCE_REGISTRY)/proven-reality-226706/redislabs:$(TRACK) +IMAGE_MAIN ?= $(SOURCE_REGISTRY)/$(PROJECT)/redislabs:$(TRACK) IMAGE_DEPLOYER_HELM ?= gcr.io/cloud-marketplace-tools/k8s/deployer_helm:$(MARKETPLACE_TOOLS_TAG) @@ -28,11 +28,13 @@ ADDITIONAL_IMAGES := deployer-helm # Should be dynamically to use $(MARKETPLACE_TOOLS_TAG) image-deployer-helm ?= $(call get_sha256,$(IMAGE_DEPLOYER_HELM)) -C2D_CONTAINER_RELEASE := $(call get_c2d_release,$(image-$(CHART_NAME))) +#C2D_CONTAINER_RELEASE := $(call get_c2d_release,$(image-$(CHART_NAME))) +C2D_CONTAINER_RELEASE ?= $(TRACK).1 BUILD_ID := $(shell date -u +%Y%m%d-%H%M%S) RELEASE ?= $(C2D_CONTAINER_RELEASE)-$(BUILD_ID) +$(info ---- C2D_CONTAINER_RELEASE = $(C2D_CONTAINER_RELEASE)) $(info ---- TRACK = $(TRACK)) $(info ---- RELEASE = $(RELEASE)) $(info ---- SOURCE_REGISTRY = $(SOURCE_REGISTRY)) diff --git a/redis/README.md b/redis/README.md index 2e4cc7f..064d251 100644 --- a/redis/README.md +++ b/redis/README.md @@ -235,7 +235,7 @@ kubectl port-forward redis-enterprise-cluster-0 8443 See [instructions here](https://docs.redislabs.com/latest/rs/faqs/) under "How to retrieve the username/password for a Redis Enterprise Cluster?" -In brief, `kubectl get secret redis-enterprise -o yaml|grep password|cut -d':' -f 2|base64 --decode` should get you the password, and you should already know the username (default admin@example.com) +In summary, `kubectl get secret redis-enterprise -o yaml|grep password|cut -d':' -f 2|base64 --decode` should get you the password, and you should already know the username (default `admin@example.com`) #### Access the Redis-Enterprise service externally diff --git a/redis/chart/redis-operator/Chart.yaml b/redis/chart/redis-operator/Chart.yaml index 5caac18..20a173a 100644 --- a/redis/chart/redis-operator/Chart.yaml +++ b/redis/chart/redis-operator/Chart.yaml @@ -1,2 +1,3 @@ name: redis-operator -version: 1.16 +version: 1.18 +# TODO update version \ No newline at end of file diff --git a/redis/chart/redis-operator/templates/redis-enterprise-cluster/redis-enterprise-cluster.yaml b/redis/chart/redis-operator/templates/redis-enterprise-cluster/redis-enterprise-cluster.yaml index d7c332c..46798bd 100644 --- a/redis/chart/redis-operator/templates/redis-enterprise-cluster/redis-enterprise-cluster.yaml +++ b/redis/chart/redis-operator/templates/redis-enterprise-cluster/redis-enterprise-cluster.yaml @@ -10,40 +10,39 @@ spec: persistentSpec: enabled: true storageClassName: "standard" - uiServiceType: ClusterIP - #TODO uiServiceType: if INGRESS_AVAILABLE + uiServiceType: {{ if .Values.ingressAvailable -}}LoadBalancer{{- else -}}ClusterIP{{- end }} username: {{ .Values.operator.redisAdmin }} redisEnterpriseNodeResources: limits: cpu: "{{ .Values.operator.nodeCpu }}m" - memory: {{ .Values.operator.nodeMem }}Gi + memory: "{{ .Values.operator.nodeMem }}Gi" requests: cpu: "{{ .Values.operator.nodeCpu }}m" - memory: {{ .Values.operator.nodeMem }}Gi -# sideContainersSpec:#TODO -# - name: ubbagent -# image: "{{ .Values.operator.ubbAgent }}" -# imagePullPolicy: IfNotPresent -# env: -# - name: NODE_CPU -# value: "{{ .Values.operator.nodeCpu }}m" -# - name: NODE_MEM -# value: {{ .Values.operator.nodeMem }}Gi -# - name: AGENT_CONFIG_FILE -# value: /etc/ubbagent/config.yaml -# - name: AGENT_LOCAL_PORT -# value: "6080" -# - name: AGENT_STATE_DIR -# value: /opt/persistent/ubbagent -# - name: AGENT_REPORT_DIR -# value: /opt/persistent/ubbagent/reports -# - name: AGENT_ENCODED_KEY -# valueFrom: -# secretKeyRef: -# name: "{{ .Release.Name }}-reporting-secret" -# key: reporting-key -# - name: AGENT_CONSUMER_ID -# valueFrom: -# secretKeyRef: -# name: "{{ .Release.Name }}-reporting-secret" -# key: consumer-id + memory: "{{ .Values.operator.nodeMem }}Gi" + sideContainersSpec: + - name: ubbagent + image: "{{ .Values.operator.ubbAgent }}" + imagePullPolicy: IfNotPresent + env: + - name: NODE_CPU + value: "{{ .Values.operator.nodeCpu }}m" + - name: NODE_MEM + value: "{{ .Values.operator.nodemem }}Gi" + - name: AGENT_CONFIG_FILE + value: /etc/ubbagent/config.yaml + - name: AGENT_LOCAL_PORT + value: "6080" + - name: AGENT_STATE_DIR + value: /opt/persistent/ubbagent + - name: AGENT_REPORT_DIR + value: /opt/persistent/ubbagent/reports + - name: AGENT_ENCODED_KEY + valueFrom: + secretKeyRef: + name: "{{ .Release.Name }}-reporting-secret" + key: reporting-key + - name: AGENT_CONSUMER_ID + valueFrom: + secretKeyRef: + name: "{{ .Release.Name }}-reporting-secret" + key: consumer-id diff --git a/redis/chart/redis-operator/values.yaml b/redis/chart/redis-operator/values.yaml index c7d8561..5ed831e 100644 --- a/redis/chart/redis-operator/values.yaml +++ b/redis/chart/redis-operator/values.yaml @@ -7,4 +7,5 @@ operator: redisAdmin: admin@example.com nodeCpu: 4000 nodeMem: 15 - ubbAgent: TODO + ubbAgent: gcr.io/cloud-marketplace-tools/metering/ubbagent:latest +ingressAvailable: true #TODO Remove \ No newline at end of file diff --git a/redis/schema.yaml b/redis/schema.yaml index d337d2e..5cc4c64 100644 --- a/redis/schema.yaml +++ b/redis/schema.yaml @@ -55,32 +55,33 @@ properties: # https://www.w3.org/TR/html52/sec-forms.html#email-state-typeemail pattern: ^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$ default: admin@example.com - # IMAGE_UBBAGENT: TODO - #type: string - #default: #"$REGISTRY/ubbagent:$TAG" - #x-google-marketplace: - # type: IMAGE - INGRESS_AVAILABLE: + imageUbbagent: + type: string + default: $REGISTRY/ubbagent:$TAG + x-google-marketplace: + type: IMAGE + ingressAvailable: type: boolean + default: true title: Ingress Supported description: Indicates whether the cluster is detected to have Ingress support. x-google-marketplace: type: INGRESS_AVAILABLE - REPORTING_SECRET: + REPORTING_SECRET: # TODO -- Is this needed? Is this used? type: string x-google-marketplace: type: REPORTING_SECRET - operator.nodeCpu: #TODO + operator.nodeCpu: title: Node CPU [millis] type: integer - description: Each node CPU in millicpu i.e. 1000 equals 1vCPU + description: Each node CPU in millicpu, i.e. 1000 equals 1vCPU default: 4000 minimum: 100 maximum: 32000 - operator.nodeMem: #TODO + operator.nodeMem: title: Node Memory [GB] type: integer - description: Each node RAM in GB ie 1 equals 1GiB + description: Each node RAM in GB, i.e., 1 equals 1GiB default: 15 minimum: 1 maximum: 269 diff --git a/tmp_Dockerfile b/tmp_Dockerfile deleted file mode 100644 index ea7a9cd..0000000 --- a/tmp_Dockerfile +++ /dev/null @@ -1 +0,0 @@ -FROM gcr.io/cloud-marketplace-tools/k8s/deployer_helm/onbuild From caf41f4e791a8b70b6fc9d800ecf487359380978 Mon Sep 17 00:00:00 2001 From: Joshua Fox <4542591+JoshuaFox@users.noreply.github.com> Date: Tue, 4 Feb 2020 14:55:55 +0200 Subject: [PATCH 052/120] ubbagent --- redis/Makefile | 2 +- redis/apptest/tester/Dockerfile | 1 + redis/apptest/tester/tests/basic-suite.yaml | 59 +++---------------- redis/chart/redis-operator/Chart.yaml | 4 +- .../redis-enterprise-cluster.yaml | 6 +- redis/chart/redis-operator/values.yaml | 6 +- redis/resources/service-accounts.yaml | 2 +- redis/schema.yaml | 6 +- 8 files changed, 23 insertions(+), 63 deletions(-) diff --git a/redis/Makefile b/redis/Makefile index 66466d6..49522c2 100644 --- a/redis/Makefile +++ b/redis/Makefile @@ -11,7 +11,7 @@ APP_ID ?= $(CHART_NAME) #SOURCE_REGISTRY ?= marketplace.gcr.io/google PROJECT=joshua-playground SOURCE_REGISTRY ?= gcr.io -TRACK ?= 1.18 +TRACK ?= 1.20 IMAGE_MAIN ?= $(SOURCE_REGISTRY)/$(PROJECT)/redislabs:$(TRACK) diff --git a/redis/apptest/tester/Dockerfile b/redis/apptest/tester/Dockerfile index 5dd3087..0eb4bcf 100644 --- a/redis/apptest/tester/Dockerfile +++ b/redis/apptest/tester/Dockerfile @@ -10,6 +10,7 @@ RUN mkdir -p /opt/kubectl/1.14 \ && chmod 755 /opt/kubectl/1.14/kubectl \ && ln -s /opt/kubectl/1.14/kubectl /usr/bin/kubectl + COPY tests/basic-suite.yaml /tests/basic-suite.yaml COPY tester.sh /tester.sh diff --git a/redis/apptest/tester/tests/basic-suite.yaml b/redis/apptest/tester/tests/basic-suite.yaml index 0f85202..aa31a8a 100644 --- a/redis/apptest/tester/tests/basic-suite.yaml +++ b/redis/apptest/tester/tests/basic-suite.yaml @@ -9,70 +9,29 @@ actions: #TODO test and write new tests bashTest: script: | timeout 120 bash -c ' - until kubectl get crd app.redislabs.com; + until kubectl get crd redisenterpriseclusters.app.redislabs.com; do echo "Waiting for RedisEnterpriseClusters CRDs created"; sleep 5; done' expect: exitCode: equals: 0 -- name: Deploy test RedisEnterpriseCluster - bashTest: - script: | - kubectl apply --namespace ${NAMESPACE} -f - < Date: Tue, 4 Feb 2020 18:32:30 +0200 Subject: [PATCH 053/120] ubbagent --- redis/apptest/tester/tests/basic-suite.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/redis/apptest/tester/tests/basic-suite.yaml b/redis/apptest/tester/tests/basic-suite.yaml index aa31a8a..1c778bd 100644 --- a/redis/apptest/tester/tests/basic-suite.yaml +++ b/redis/apptest/tester/tests/basic-suite.yaml @@ -25,12 +25,12 @@ actions: #TODO test and write new tests expect: exitCode: equals: 0 -- name: Waiting for redis-enterprise-services-rigger pods to be created +- name: Waiting for redis-operator deployment to be created bashTest: script: | timeout 120 bash -c ' - until kubectl get pod -n ${NAMESPACE} | grep redis-enterprise-services-rigger - do echo "Waiting for redis-enterprise-services-rigger pods to be created"; sleep 5; + until kubectl get deployment -n ${NAMESPACE} | grep redis-operator + do echo "Waiting for redis-operator deployment to be created"; sleep 5; done' expect: exitCode: From 85fdbc21d604a608271d15a3bb52241d2f09956c Mon Sep 17 00:00:00 2001 From: Sambasiva Andaluri Date: Mon, 22 Mar 2021 20:27:21 -0400 Subject: [PATCH 054/120] GKE marketplace mostly works --- MARKETPLACE_TOOLS_TAG | 1 - Makefile | 124 ++ redis/README.md => README.md | 10 +- app.Makefile | 116 -- app_v2.Makefile | 146 -- .../redis-operator/templates/tester.yaml | 0 .../apptest => apptest}/deployer/schema.yaml | 0 {redis/apptest => apptest}/tester/Dockerfile | 0 {redis/apptest => apptest}/tester/tester.sh | 0 .../tester/tests/basic-suite.yaml | 0 billing-agent/Dockerfile | 10 + billing-agent/receiver.py | 12 + c2d_deployer.Makefile | 137 -- .../chart => chart}/redis-operator/Chart.yaml | 0 chart/redis-operator/files/crd/rec_crd.yaml | 1706 +++++++++++++++++ chart/redis-operator/files/crd/redb_crd.yaml | 550 ++++++ .../chart => chart}/redis-operator/logo.png | Bin .../redis-operator/templates/_helpers.tpl | 12 + .../redis-operator/templates/application.yaml | 0 .../templates/configmap/cr.yaml | 32 + .../templates/configmap/crd.yaml | 0 .../templates/configmap/ubbagent.yaml | 37 + .../templates/deployment/operator.yaml | 90 + .../templates/job/cr-create.yaml | 38 + .../templates/job/crd-create.yaml | 0 chart/redis-operator/values.yaml | 11 + common.Makefile | 43 - crd.Makefile | 20 - {redis/deployer => deployer}/Dockerfile | 2 +- gcloud.Makefile | 38 - images.Makefile | 10 - redis/Makefile | 58 - ...s.com_redisenterpriseclusters.app_crd.yaml | 15 - .../templates/deployment/operator.yaml | 43 - .../redis-operator/templates/rbac/rbac.yaml | 34 - .../redis-enterprise-cluster.yaml | 48 - redis/chart/redis-operator/values.yaml | 11 - redis/resources/service-accounts.yaml | 132 -- redis/schema.yaml => schema.yaml | 84 +- usage-meter/Dockerfile | 12 + usage-meter/common.py | 89 + usage-meter/meter.py | 180 ++ usage-meter/run.sh | 7 + usage-meter/tier_pricing.py | 21 + var.Makefile | 65 - 45 files changed, 3012 insertions(+), 932 deletions(-) delete mode 100644 MARKETPLACE_TOOLS_TAG create mode 100644 Makefile rename redis/README.md => README.md (97%) delete mode 100644 app.Makefile delete mode 100644 app_v2.Makefile rename {redis/apptest => apptest}/deployer/redis-operator/templates/tester.yaml (100%) rename {redis/apptest => apptest}/deployer/schema.yaml (100%) rename {redis/apptest => apptest}/tester/Dockerfile (100%) rename {redis/apptest => apptest}/tester/tester.sh (100%) rename {redis/apptest => apptest}/tester/tests/basic-suite.yaml (100%) create mode 100644 billing-agent/Dockerfile create mode 100644 billing-agent/receiver.py delete mode 100644 c2d_deployer.Makefile rename {redis/chart => chart}/redis-operator/Chart.yaml (100%) create mode 100644 chart/redis-operator/files/crd/rec_crd.yaml create mode 100644 chart/redis-operator/files/crd/redb_crd.yaml rename {redis/chart => chart}/redis-operator/logo.png (100%) rename {redis/chart => chart}/redis-operator/templates/_helpers.tpl (69%) rename {redis/chart => chart}/redis-operator/templates/application.yaml (100%) create mode 100644 chart/redis-operator/templates/configmap/cr.yaml rename {redis/chart => chart}/redis-operator/templates/configmap/crd.yaml (100%) create mode 100644 chart/redis-operator/templates/configmap/ubbagent.yaml create mode 100644 chart/redis-operator/templates/deployment/operator.yaml create mode 100644 chart/redis-operator/templates/job/cr-create.yaml rename {redis/chart => chart}/redis-operator/templates/job/crd-create.yaml (100%) create mode 100644 chart/redis-operator/values.yaml delete mode 100644 common.Makefile delete mode 100644 crd.Makefile rename {redis/deployer => deployer}/Dockerfile (95%) delete mode 100644 gcloud.Makefile delete mode 100644 images.Makefile delete mode 100644 redis/Makefile delete mode 100644 redis/chart/redis-operator/files/crd/redislabs.com_redisenterpriseclusters.app_crd.yaml delete mode 100644 redis/chart/redis-operator/templates/deployment/operator.yaml delete mode 100644 redis/chart/redis-operator/templates/rbac/rbac.yaml delete mode 100644 redis/chart/redis-operator/templates/redis-enterprise-cluster/redis-enterprise-cluster.yaml delete mode 100644 redis/chart/redis-operator/values.yaml delete mode 100644 redis/resources/service-accounts.yaml rename redis/schema.yaml => schema.yaml (50%) create mode 100644 usage-meter/Dockerfile create mode 100644 usage-meter/common.py create mode 100644 usage-meter/meter.py create mode 100755 usage-meter/run.sh create mode 100644 usage-meter/tier_pricing.py delete mode 100644 var.Makefile diff --git a/MARKETPLACE_TOOLS_TAG b/MARKETPLACE_TOOLS_TAG deleted file mode 100644 index b0bb878..0000000 --- a/MARKETPLACE_TOOLS_TAG +++ /dev/null @@ -1 +0,0 @@ -0.9.5 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f8fa8c5 --- /dev/null +++ b/Makefile @@ -0,0 +1,124 @@ +# app.Makefile provides the main targets for installing the application. +# It requires several APP_* variables defined as followed. +include ../app.Makefile +# crd.Makefile provides targets to install Application CRD. +include ../crd.Makefile +# gcloud.Makefile provides default values for REGISTRY and NAMESPACE derived from local +# gcloud and kubectl environments. +include ../gcloud.Makefile +include ../var.Makefile + +#REGISTRY ?= marketplace.gcr.io/google/redis-enterprise-operator +REGISTRY := us-central1-docker.pkg.dev/sam-playground-290106/redis-mp +$(info ---- REGISTRY = $(REGISTRY)) + +CHART_NAME := redis-operator +$(info ---- CHART_NAME = $(CHART_NAME)) + +REDIS_TAG ?= 6.0.12-57 +$(info ---- REDIS_TAG = $(REDIS_TAG)) + +OPERATOR_TAG ?= 6.0.12-5 +$(info ---- OPERATOR_TAG = $(OPERATOR_TAG)) + +APP_DEPLOYER_IMAGE := $(REGISTRY)/deployer:$(OPERATOR_TAG) + +NAME ?= redis-enterprise-operator-1 +NAMESPACE ?= redis + +APP_PARAMETERS ?= { \ + "APP_INSTANCE_NAME": "$(NAME)", \ + "NAMESPACE": "$(NAMESPACE)" \ +} + +TESTER_IMAGE ?= $(REGISTRY)/tester:$(OPERATOR_TAG) + +app/build:: .build/redis-enterprise-operator/deployer \ + .build/redis-enterprise-operator/redis \ + .build/redis-enterprise-operator/operator \ + .build/redis-enterprise-operator/k8s-controller \ + .build/redis-enterprise-operator/usage-meter \ + .build/redis-enterprise-operator/billing-agent \ + .build/redis-enterprise-operator/tester + + +.build/redis-enterprise-operator: | .build + mkdir -p "$@" + +.build/redis-enterprise-operator/deployer: deployer/* \ + chart/**/* \ + schema.yaml \ + .build/var/APP_DEPLOYER_IMAGE \ + .build/var/MARKETPLACE_TOOLS_TAG \ + .build/var/REGISTRY \ + .build/var/OPERATOR_TAG \ + .build/var/CHART_NAME \ + | .build/redis-enterprise-operator + $(call print_target, $@) + docker build \ + --build-arg REGISTRY="$(REGISTRY)" \ + --build-arg TAG="$(OPERATOR_TAG)" \ + --build-arg CHART_NAME="$(CHART_NAME)" \ + --build-arg MARKETPLACE_TOOLS_TAG="$(MARKETPLACE_TOOLS_TAG)" \ + --tag "$(APP_DEPLOYER_IMAGE)" \ + -f deployer/Dockerfile \ + . + docker push "$(APP_DEPLOYER_IMAGE)" + @touch "$@" + +.build/redis-enterprise-operator/tester: apptest/**/* \ + | .build/redis-enterprise-operator + $(call print_target, $@) + cd apptest/tester \ + && docker build --tag "$(TESTER_IMAGE)" . + docker push "$(TESTER_IMAGE)" + @touch "$@" + +.build/redis-enterprise-operator/usage-meter: usage-meter/**/* \ + .build/var/REGISTRY \ + .build/var/OPERATOR_TAG \ + | .build/redis-enterprise-operator + $(call print_target, $@) + cd usage-meter \ + && docker build --tag "$(REGISTRY)/usagemeter:$(OPERATOR_TAG)" . + docker push "$(REGISTRY)/usagemeter:$(OPERATOR_TAG)" + @touch "$@" + +.build/redis-enterprise-operator/billing-agent: billing-agent/**/* \ + .build/var/REGISTRY \ + .build/var/OPERATOR_TAG \ + | .build/redis-enterprise-operator + $(call print_target, $@) + cd billing-agent \ + && docker build --tag "$(REGISTRY)/billingagent:$(OPERATOR_TAG)" . + docker push "$(REGISTRY)/billingagent:$(OPERATOR_TAG)" + @touch "$@" + +.build/redis-enterprise-operator/redis: .build/var/REGISTRY \ + .build/var/REDIS_TAG \ + | .build/redis-enterprise-operator + $(call print_target, $@) + docker pull redislabs/redis:$(REDIS_TAG) + docker tag redislabs/redis:$(REDIS_TAG) "$(REGISTRY)/redis:$(REDIS_TAG)" + docker push "$(REGISTRY)/redis:$(REDIS_TAG)" + @touch "$@" + +.build/redis-enterprise-operator/operator: .build/var/REGISTRY \ + .build/var/OPERATOR_TAG \ + | .build/redis-enterprise-operator + $(call print_target, $@) + docker pull redislabs/operator:$(OPERATOR_TAG) + docker tag redislabs/operator:$(OPERATOR_TAG) "$(REGISTRY)/operator:$(OPERATOR_TAG)" + docker push "$(REGISTRY)/operator:$(OPERATOR_TAG)" + @touch "$@" + +.build/redis-enterprise-operator/k8s-controller: .build/var/REGISTRY \ + .build/var/OPERATOR_TAG \ + | .build/redis-enterprise-operator + $(call print_target, $@) + docker pull redislabs/k8s-controller:$(OPERATOR_TAG) + docker tag redislabs/k8s-controller:$(OPERATOR_TAG) "$(REGISTRY)/k8s-controller:$(OPERATOR_TAG)" + docker push "$(REGISTRY)/k8s-controller:$(OPERATOR_TAG)" + @touch "$@" + + diff --git a/redis/README.md b/README.md similarity index 97% rename from redis/README.md rename to README.md index 064d251..71032d1 100644 --- a/redis/README.md +++ b/README.md @@ -1,9 +1,6 @@ # Overview - - -This bundles [Redis Enterprise](https://www.redislabs.com/) into a form suited to Google Cloud Platform Marketplace. - +This repo is for building and deploying [Redis Enterprise](https://github.com/RedisLabs/redis-enterprise-k8s-docs) for GKE Market Place. ## Design @@ -18,10 +15,7 @@ The deployment creates two services: - Service discovery: a headless service for connections between the Redis-Enterprise nodes. -Redis-Enterprise Kubernetes application has the following ports configured: [TODO] - - -# Installation +# Build instructions ## Quick install with Google Cloud Marketplace diff --git a/app.Makefile b/app.Makefile deleted file mode 100644 index a853bfb..0000000 --- a/app.Makefile +++ /dev/null @@ -1,116 +0,0 @@ -ifndef __APP_MAKEFILE__ - -__APP_MAKEFILE__ := included - - -makefile_dir := $(dir $(realpath $(lastword $(MAKEFILE_LIST)))) -include $(makefile_dir)/common.Makefile -include $(makefile_dir)/var.Makefile - -VERIFY_WAIT_TIMEOUT = 600 - -##### Helper functions ##### - -# Extracts the name property from APP_PARAMETERS. -define name_parameter -$(shell echo '$(APP_PARAMETERS)' \ - | docker run -i --entrypoint=/bin/print_config.py --rm $(APP_DEPLOYER_IMAGE) --values_mode stdin --xtype NAME) -endef - - -# Extracts the namespace property from APP_PARAMETERS. -define namespace_parameter -$(shell echo '$(APP_PARAMETERS)' \ - | docker run -i --entrypoint=/bin/print_config.py --rm $(APP_DEPLOYER_IMAGE) --values_mode stdin --xtype NAMESPACE) -endef - - -##### Helper targets ##### - - -.build/app: | .build - mkdir -p "$@" - - -# (1) Always update the dev script to make sure it's up to date. -# There isn't currently a way to detect if the dev container has changed. -# (2) The mpdev script is first copied to the / tmp directory and -# then moved to the target path due to the "Text file busy" error. -.PHONY: .build/app/dev -.build/app/dev: .build/var/MARKETPLACE_TOOLS_TAG \ - | .build/app - @docker run \ - "gcr.io/cloud-marketplace-tools/k8s/dev:$(MARKETPLACE_TOOLS_TAG)" \ - cat /scripts/dev > "/tmp/dev" - @mv "/tmp/dev" "$@" - @chmod a+x "$@" - - -########### Main targets ########### - - -# Builds the application containers and push them to the registry. -# Including Makefile can extend this target. This target is -# a prerequisite for install. -.PHONY: app/build -app/build:: ; - - -# Installs the application into target namespace on the cluster. -.PHONY: app/install -app/install:: app/build \ - .build/var/APP_DEPLOYER_IMAGE \ - .build/var/APP_PARAMETERS \ - .build/var/MARKETPLACE_TOOLS_TAG \ - | .build/app/dev - $(call print_target) - .build/app/dev install \ - --deployer='$(APP_DEPLOYER_IMAGE)' \ - --parameters='$(APP_PARAMETERS)' \ - --entrypoint="/bin/deploy.sh" - - -# Installs the application into target namespace on the cluster. -.PHONY: app/install-test -app/install-test:: app/build \ - .build/var/APP_DEPLOYER_IMAGE \ - .build/var/APP_PARAMETERS \ - .build/var/MARKETPLACE_TOOLS_TAG \ - | .build/app/dev - $(call print_target) - .build/app/dev install \ - --deployer='$(APP_DEPLOYER_IMAGE)' \ - --parameters='$(APP_PARAMETERS)' \ - --entrypoint="/bin/deploy_with_tests.sh" - - -# Uninstalls the application from the target namespace on the cluster. -.PHONY: app/uninstall -app/uninstall: .build/var/APP_DEPLOYER_IMAGE \ - .build/var/APP_PARAMETERS - $(call print_target) - kubectl delete 'application/$(NAME)' \ - --namespace='$(NAMESPACE)' \ - --ignore-not-found - - -# Runs the verification pipeline. -.PHONY: app/verify -app/verify: app/build \ - .build/var/APP_DEPLOYER_IMAGE \ - .build/var/MARKETPLACE_TOOLS_TAG \ - | .build/app/dev - $(call print_target) - .build/app/dev verify \ - --deployer='$(APP_DEPLOYER_IMAGE)' \ - --wait_timeout="$(VERIFY_WAIT_TIMEOUT)" - - -# Runs diagnostic tool to make sure your environment is properly setup. -.PHONY: app/doctor -app/doctor: | .build/app/dev - $(call print_target) - .build/app/dev doctor - - -endif diff --git a/app_v2.Makefile b/app_v2.Makefile deleted file mode 100644 index cb40dd9..0000000 --- a/app_v2.Makefile +++ /dev/null @@ -1,146 +0,0 @@ -# -# Used with deployer schema v2. -# - -ifndef __APP_V2_MAKEFILE__ - -__APP_V2_MAKEFILE__ := included - - -makefile_dir := $(dir $(realpath $(lastword $(MAKEFILE_LIST)))) -include $(makefile_dir)/common.Makefile -include $(makefile_dir)/var.Makefile - -VERIFY_WAIT_TIMEOUT = 600 - - -##### Validations and Information ##### - -ifndef APP_GCS_PATH -$(error APP_GCS_PATH must be defined) -endif - -$(info ---- APP_GCS_PATH = $(APP_GCS_PATH)) - -ifndef APP_DEPLOYER_IMAGE -$(error APP_DEPLOYER_IMAGE must be defined) -endif - -$(info ---- APP_DEPLOYER_IMAGE = $(APP_DEPLOYER_IMAGE)) - - -##### Helper functions ##### - -# Extracts the name property from APP_PARAMETERS. -define name_parameter -$(shell echo '$(APP_PARAMETERS)' \ - | docker run -i --entrypoint=/bin/print_config.py --rm $(APP_DEPLOYER_IMAGE) --values_mode stdin --xtype NAME) -endef - - -# Extracts the namespace property from APP_PARAMETERS. -define namespace_parameter -$(shell echo '$(APP_PARAMETERS)' \ - | docker run -i --entrypoint=/bin/print_config.py --rm $(APP_DEPLOYER_IMAGE) --values_mode stdin --xtype NAMESPACE) -endef - - -##### Helper targets ##### - - -.build/app: | .build - mkdir -p "$@" - - -# (1) Always update the dev script to make sure it's up to date. -# There isn't currently a way to detect if the dev container has changed. -# (2) The mpdev script is first copied to the / tmp directory and -# then moved to the target path due to the "Text file busy" error. -.PHONY: .build/app/dev -.build/app/dev: .build/var/MARKETPLACE_TOOLS_TAG \ - | .build/app - @docker run \ - "gcr.io/cloud-marketplace-tools/k8s/dev:$(MARKETPLACE_TOOLS_TAG)" \ - cat /scripts/dev > "/tmp/dev" - @mv "/tmp/dev" "$@" - @chmod a+x "$@" - - -########### Main targets ########### - - -# Builds the application containers and push them to the registry. -# Including Makefile can extend this target. This target is -# a prerequisite for install. -.PHONY: app/build -app/build:: ; - - -.PHONY: app/publish -app/publish:: app/build \ - .build/var/APP_DEPLOYER_IMAGE \ - .build/var/APP_GCS_PATH \ - .build/var/MARKETPLACE_TOOLS_TAG \ - | .build/app/dev - $(call print_target) - .build/app/dev publish \ - --deployer_image='$(APP_DEPLOYER_IMAGE)' \ - --gcs_repo='$(APP_GCS_PATH)' - - -# Installs the application into target namespace on the cluster. -.PHONY: app/install -app/install:: app/publish \ - .build/var/APP_DEPLOYER_IMAGE \ - .build/var/APP_PARAMETERS \ - .build/var/MARKETPLACE_TOOLS_TAG \ - | .build/app/dev - $(call print_target) - .build/app/dev install \ - --version_meta_file='$(APP_GCS_PATH)/$(RELEASE).yaml' \ - --parameters='$(APP_PARAMETERS)' - -# Installs the application into target namespace on the cluster. -.PHONY: app/install-test -app/install-test:: app/publish \ - .build/var/APP_DEPLOYER_IMAGE \ - .build/var/APP_PARAMETERS \ - .build/var/MARKETPLACE_TOOLS_TAG \ - | .build/app/dev - $(call print_target) - .build/app/dev install \ - --deployer='$(APP_DEPLOYER_IMAGE)' \ - --parameters='$(APP_PARAMETERS)' \ - --entrypoint="/bin/deploy_with_tests.sh" - - -# Uninstalls the application from the target namespace on the cluster. -.PHONY: app/uninstall -app/uninstall: .build/var/APP_DEPLOYER_IMAGE \ - .build/var/APP_PARAMETERS - $(call print_target) - kubectl delete 'application/$(NAME)' \ - --namespace='$(NAMESPACE)' \ - --ignore-not-found - - -# Runs the verification pipeline. -.PHONY: app/verify -app/verify: app/publish \ - .build/var/APP_DEPLOYER_IMAGE \ - .build/var/MARKETPLACE_TOOLS_TAG \ - | .build/app/dev - $(call print_target) - .build/app/dev verify \ - --deployer='$(APP_DEPLOYER_IMAGE)' \ - --wait_timeout="$(VERIFY_WAIT_TIMEOUT)" - - -# Runs diagnostic tool to make sure your environment is properly setup. -.PHONY: app/doctor -app/doctor: | .build/app/dev - $(call print_target) - .build/app/dev doctor - - -endif diff --git a/redis/apptest/deployer/redis-operator/templates/tester.yaml b/apptest/deployer/redis-operator/templates/tester.yaml similarity index 100% rename from redis/apptest/deployer/redis-operator/templates/tester.yaml rename to apptest/deployer/redis-operator/templates/tester.yaml diff --git a/redis/apptest/deployer/schema.yaml b/apptest/deployer/schema.yaml similarity index 100% rename from redis/apptest/deployer/schema.yaml rename to apptest/deployer/schema.yaml diff --git a/redis/apptest/tester/Dockerfile b/apptest/tester/Dockerfile similarity index 100% rename from redis/apptest/tester/Dockerfile rename to apptest/tester/Dockerfile diff --git a/redis/apptest/tester/tester.sh b/apptest/tester/tester.sh similarity index 100% rename from redis/apptest/tester/tester.sh rename to apptest/tester/tester.sh diff --git a/redis/apptest/tester/tests/basic-suite.yaml b/apptest/tester/tests/basic-suite.yaml similarity index 100% rename from redis/apptest/tester/tests/basic-suite.yaml rename to apptest/tester/tests/basic-suite.yaml diff --git a/billing-agent/Dockerfile b/billing-agent/Dockerfile new file mode 100644 index 0000000..3d51e2e --- /dev/null +++ b/billing-agent/Dockerfile @@ -0,0 +1,10 @@ +FROM python:3.8-slim + +RUN apt-get update && apt-get install -y curl +RUN pip install flask +RUN mkdir -p /app +COPY receiver.py /app/ +WORKDIR /app +ENV FLASK_APP=receiver.py + +ENTRYPOINT ["flask", "run", "--host", "0.0.0.0", "--port", "8888"] diff --git a/billing-agent/receiver.py b/billing-agent/receiver.py new file mode 100644 index 0000000..6451f05 --- /dev/null +++ b/billing-agent/receiver.py @@ -0,0 +1,12 @@ +from flask import Flask +from flask import request, jsonify + +app = Flask(__name__) + +@app.route('/',methods=['POST']) +def root(): + if request.method=='POST': + print(request.json,flush=True) + return jsonify({'status':'OK'}), 200 + else: + return "Not allowed",405 diff --git a/c2d_deployer.Makefile b/c2d_deployer.Makefile deleted file mode 100644 index 763c0c6..0000000 --- a/c2d_deployer.Makefile +++ /dev/null @@ -1,137 +0,0 @@ -# -# Used with app_v2 Makefile for deployer schema v2. -# - -ifndef __C2D_DEPLOYER_MAKEFILE__ - -__C2D_DEPLOYER_MAKEFILE__:= included - -##### Check required variables ##### - - -ifndef CHART_NAME -$(error CHART_NAME must be defined) -endif - -$(info ---- CHART_NAME = $(CHART_NAME)) - -ifndef APP_ID -$(error APP_ID must be defined) -endif - -$(info ---- APP_ID = $(APP_ID)) - -ifndef image-$(CHART_NAME) -$(error image-$(CHART_NAME) must be defined) -endif - -$(info ---- image-$(CHART_NAME) = $(image-$(CHART_NAME))) - - -##### Common variables ##### - -APP_DEPLOYER_IMAGE ?= $(REGISTRY)/$(APP_ID)/deployer:$(RELEASE) -APP_DEPLOYER_IMAGE_TRACK_TAG ?= $(REGISTRY)/$(APP_ID)/deployer:$(TRACK) -APP_TESTER_IMAGE ?= $(REGISTRY)/$(APP_ID)/tester:$(RELEASE) -APP_GCS_PATH ?= $(GCS_URL)/$(APP_ID)/$(TRACK) - - -include ../app_v2.Makefile - - -$(info ---- TRACK = $(TRACK)) -$(info ---- RELEASE = $(RELEASE)) - - -##### Helper targets ##### - -.build/$(CHART_NAME): | .build - mkdir -p "$@" - - -app/build:: .build/$(CHART_NAME)/VERSION \ - .build/$(CHART_NAME)/$(CHART_NAME) \ - .build/$(CHART_NAME)/images \ - .build/$(CHART_NAME)/deployer - - -.build/$(CHART_NAME)/deployer: deployer/* \ - chart/$(CHART_NAME)/* \ - chart/$(CHART_NAME)/templates/* \ - schema.yaml \ - .build/var/APP_DEPLOYER_IMAGE \ - .build/var/APP_DEPLOYER_IMAGE_TRACK_TAG \ - .build/var/MARKETPLACE_TOOLS_TAG \ - .build/var/REGISTRY \ - .build/var/TRACK \ - .build/var/RELEASE \ - | .build/$(CHART_NAME) - $(call print_target,$@) - docker build \ - --build-arg REGISTRY="$(REGISTRY)/$(APP_ID)" \ - --build-arg TAG="$(RELEASE)" \ - --build-arg CHART_NAME="$(CHART_NAME)" \ - --build-arg MARKETPLACE_TOOLS_TAG="$(MARKETPLACE_TOOLS_TAG)" \ - --tag "$(APP_DEPLOYER_IMAGE)" \ - -f deployer/Dockerfile \ - . - docker tag "$(APP_DEPLOYER_IMAGE)" "$(APP_DEPLOYER_IMAGE_TRACK_TAG)" - docker push "$(APP_DEPLOYER_IMAGE)" - docker push "$(APP_DEPLOYER_IMAGE_TRACK_TAG)" - @touch "$@" - - -.build/$(CHART_NAME)/$(CHART_NAME): .build/var/REGISTRY \ - .build/var/TRACK \ - .build/var/RELEASE \ - | .build/$(CHART_NAME) - $(call print_target,$@) - docker pull $(image-$(CHART_NAME)) - docker tag $(image-$(CHART_NAME)) "$(REGISTRY)/$(APP_ID):$(TRACK)" - docker tag $(image-$(CHART_NAME)) "$(REGISTRY)/$(APP_ID):$(RELEASE)" - docker push "$(REGISTRY)/$(APP_ID):$(TRACK)" - docker push "$(REGISTRY)/$(APP_ID):$(RELEASE)" - @touch "$@" - - -# map every element of ADDITIONAL_IMAGES list -# from [image-name] to .build/$(CHART_NAME)/[image-name] format -IMAGE_TARGETS_LIST = $(patsubst %,.build/$(CHART_NAME)/%,$(ADDITIONAL_IMAGES)) -.build/$(CHART_NAME)/images: $(IMAGE_TARGETS_LIST) - -# extract image name from rule with .build/$(CHART_NAME)/% -# and use % match as $* in recipe -$(IMAGE_TARGETS_LIST): .build/$(CHART_NAME)/%: .build/var/REGISTRY \ - .build/var/TRACK \ - .build/var/RELEASE \ - | .build/$(CHART_NAME) - $(call print_target,$*) - docker pull $(image-$*) - docker tag $(image-$*) "$(REGISTRY)/$(APP_ID)/$*:$(TRACK)" - docker tag $(image-$*) "$(REGISTRY)/$(APP_ID)/$*:$(RELEASE)" - docker push "$(REGISTRY)/$(APP_ID)/$*:$(TRACK)" - docker push "$(REGISTRY)/$(APP_ID)/$*:$(RELEASE)" - @touch "$@" - - -.build/$(CHART_NAME)/tester: .build/var/APP_TESTER_IMAGE \ - $(shell find apptest -type f) \ - | .build/$(CHART_NAME) - $(call print_target,$@) - cd apptest/tester \ - && docker build --tag "$(APP_TESTER_IMAGE)" . - docker push "$(APP_TESTER_IMAGE)" - @touch "$@" - - -########### Main targets ########### - - -.PHONY: .build/$(CHART_NAME)/VERSION -.build/$(CHART_NAME)/VERSION: - $(call print_target,$@) - @echo "$(C2D_CONTAINER_RELEASE)" | grep -qE "^$(TRACK).[0-9]+$$" || \ - ( echo "C2D_RELEASE doesn't start with TRACK or doesn't match TRACK exactly"; exit 1 ) - - -endif diff --git a/redis/chart/redis-operator/Chart.yaml b/chart/redis-operator/Chart.yaml similarity index 100% rename from redis/chart/redis-operator/Chart.yaml rename to chart/redis-operator/Chart.yaml diff --git a/chart/redis-operator/files/crd/rec_crd.yaml b/chart/redis-operator/files/crd/rec_crd.yaml new file mode 100644 index 0000000..9788a7a --- /dev/null +++ b/chart/redis-operator/files/crd/rec_crd.yaml @@ -0,0 +1,1706 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: redisenterpriseclusters.app.redislabs.com +spec: + additionalPrinterColumns: + - JSONPath: .spec.nodes + name: Nodes + type: string + - JSONPath: .spec.redisEnterpriseImageSpec.versionTag + name: Version + type: string + - JSONPath: .status.state + name: State + type: string + - JSONPath: .status.specStatus + name: Spec Status + type: string + - JSONPath: .status.licenseStatus.licenseState + name: License State + type: string + - JSONPath: .status.licenseStatus.shardsLimit + name: Shards Limit + type: string + - JSONPath: .status.licenseStatus.expirationDate + name: License Expiration Date + type: string + - name: Age + type: date + JSONPath: .metadata.creationTimestamp + group: app.redislabs.com + names: + kind: RedisEnterpriseCluster + listKind: RedisEnterpriseClusterList + plural: redisenterpriseclusters + singular: redisenterprisecluster + shortNames: + - rec + scope: Namespaced + subresources: + status: {} + version: v1alpha1 + versions: + - name: v1alpha1 + served: true + storage: true + - name: v1 + served: true + storage: false + validation: + openAPIV3Schema: + description: RedisEnterpriseCluster is the Schema for the redisenterpriseclusters + API + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + description: RedisEnterpriseClusterSpec defines the desired state of RedisEnterpriseCluster + properties: + activeActive: + description: Specification for ActiveActive setup + properties: + apiIngressUrl: + description: RS API URL + type: string + dbIngressSuffix: + description: DB ENDPOINT SUFFIX - will be used to set the db host. + ingress Creates a host name so it + should be unique if more than one db is created on the cluster + with the same name + type: string + ingressAnnotations: + additionalProperties: + type: string + description: Used for ingress controllers such as ha-proxy or nginx + in GKE + type: object + method: + description: Used to distinguish between different platforms implementation + enum: + - openShiftRoute + - ingress + type: string + required: + - apiIngressUrl + - dbIngressSuffix + - method + type: object + antiAffinityAdditionalTopologyKeys: + description: Additional antiAffinity terms in order to support installation + on different zones/vcenters + items: + type: string + type: array + bootstrapperImageSpec: + description: Specification for Bootstrapper container image + properties: + imagePullPolicy: + description: PullPolicy describes a policy for if/when to pull a + container image + type: string + repository: + description: Repository + type: string + versionTag: + type: string + type: object + bootstrapperResources: + description: Compute resource requirements for bootstrapper containers + 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-compute-resources-container/' + 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-compute-resources-container/' + type: object + type: object + clusterRecovery: + description: ClusterRecovery initiates cluster recovery when set to + true. Note that this field is cleared automatically after the cluster + is recovered + type: boolean + createServiceAccount: + description: Whether to create service account + type: boolean + enforceIPv4: + description: Sets ENFORCE_IPV4 environment variable + type: boolean + extraLabels: + additionalProperties: + type: string + description: Labels that the user defines for their convenience + type: object + hostAliases: + items: + description: HostAlias holds the mapping between IP and hostnames + that will be injected as an entry in the pod's hosts file. + properties: + hostnames: + description: Hostnames for the above IP address. + items: + type: string + type: array + ip: + description: IP address of the host file entry. + type: string + type: object + type: array + license: + description: Redis Enterprise License + type: string + nodeSelector: + additionalProperties: + type: string + description: Selector for nodes that could fit Redis Enterprise pod + type: object + nodes: + description: Number of Redis Enterprise nodes (pods) + format: int32 + type: integer + persistentSpec: + description: Specification for Redis Enterprise Cluster persistence + properties: + enabled: + description: Whether to add persistent volume to Redis Enterprise + pods + type: boolean + storageClassName: + description: Storage class for persistent volume in Redis Enterprise + pods Leave empty to use the default + type: string + volumeSize: + 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 + type: object + podAnnotations: + additionalProperties: + type: string + description: pod annotations + type: object + podAntiAffinity: + description: 'Override for the default anti-affinity rules of the Redis + Enterprise pods. + More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#an-example-of-a-pod-that-uses-pod-affinity' + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + type: object + podSecurityPolicyName: + description: Name of pod security policy to use on pods See https://kubernetes.io/docs/concepts/policy/pod-security-policy/ + type: string + podTolerations: + description: 'Tolerations that are added to all managed pods. More + information: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/' + items: + properties: + effect: + type: string + key: + type: string + operator: + type: string + tolerationSeconds: + format: int64 + type: integer + value: + type: string + type: object + type: array + priorityClassName: + description: Adds the priority class to pods managed by the operator + type: string + pullSecrets: + description: 'PullSecrets is an optional list of references to secrets + in the same namespace to use for pulling any of the images. If specified, + these secrets will be passed to individual puller implementations + for them to use. More info: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/' + items: + properties: + name: + description: 'Secret name' + type: string + type: object + type: array + rackAwarenessNodeLabel: + description: Node label that specifies rack ID - if specified, will + create rack aware cluster. Rack awareness requires node label must + exist on all nodes. Additionally, operator needs a special cluster + role with permission to list nodes. + type: string + redisEnterpriseImageSpec: + description: Specification for Redis Enterprise container image + properties: + imagePullPolicy: + description: PullPolicy describes a policy for if/when to pull a + container image + type: string + repository: + description: Repository + type: string + versionTag: + type: string + type: object + redisEnterpriseNodeResources: + description: Compute resource requirements for Redis Enterprise containers + 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-compute-resources-container/' + 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-compute-resources-container/' + type: object + type: object + redisEnterpriseServicesRiggerImageSpec: + description: Specification for Services Rigger container image + properties: + imagePullPolicy: + description: PullPolicy describes a policy for if/when to pull a + container image + type: string + repository: + description: Repository + type: string + versionTag: + type: string + type: object + redisEnterpriseServicesRiggerResources: + description: Compute resource requirements for Services Rigger pod + 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-compute-resources-container/' + 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-compute-resources-container/' + type: object + type: object + redisEnterpriseVolumeMounts: + description: 'additional volume mounts within the redis enterprise containers. + More info: https://kubernetes.io/docs/concepts/storage/volumes/' + items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: array + serviceAccountName: + description: Name of the service account to use + type: string + servicesRiggerSpec: + description: Specification for service rigger + properties: + databaseServiceType: + description: Service types for access to databases. should be a + comma separated list. The possible values are cluster_ip, headless + and load_balancer. + type: string + extraEnvVars: + items: + description: 'EnvVar represents an environment variable present + in a Container. + More info: https://kubernetes.io/docs/tasks/inject-data-application/environment-variable-expose-pod-information/' + properties: + name: + description: Name of the environment variable. + type: string + value: + 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 + type: string + optional: + description: Specify whether the ConfigMap or its + key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: Selects a field of the pod + 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 + resourceFieldRef: + description: 'Selects a resource of the container: only + resources limits and requests 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 + 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 + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + serviceNaming: + enum: + - bdb_name + - redis-port + type: string + type: object + sideContainersSpec: + items: + properties: + args: + items: + type: string + type: array + command: + items: + type: string + type: array + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + resourceFieldRef: + properties: + containerName: + type: string + divisor: + 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 + resource: + type: string + required: + - resource + type: object + secretKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + envFrom: + items: + properties: + configMapRef: + properties: + name: + type: string + optional: + type: boolean + type: object + prefix: + type: string + secretRef: + properties: + name: + type: string + optional: + type: boolean + type: object + type: object + type: array + image: + type: string + imagePullPolicy: + type: string + lifecycle: + properties: + postStart: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + type: object + livenessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + failureThreshold: + format: int32 + type: integer + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + timeoutSeconds: + format: int32 + type: integer + type: object + name: + type: string + ports: + items: + properties: + containerPort: + format: int32 + type: integer + hostIP: + type: string + hostPort: + format: int32 + type: integer + name: + type: string + protocol: + type: string + required: + - containerPort + type: object + type: array + readinessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + failureThreshold: + format: int32 + type: integer + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + timeoutSeconds: + format: int32 + type: integer + type: object + 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 + 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 + type: object + type: object + securityContext: + properties: + allowPrivilegeEscalation: + type: boolean + capabilities: + properties: + add: + items: + type: string + type: array + drop: + items: + type: string + type: array + type: object + privileged: + type: boolean + procMount: + type: string + readOnlyRootFilesystem: + type: boolean + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + runAsUserName: + type: string + type: object + type: object + startupProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + failureThreshold: + format: int32 + type: integer + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + timeoutSeconds: + format: int32 + type: integer + type: object + stdin: + type: boolean + stdinOnce: + type: boolean + terminationMessagePath: + type: string + terminationMessagePolicy: + type: string + tty: + type: boolean + volumeDevices: + items: + properties: + devicePath: + type: string + name: + type: string + required: + - devicePath + - name + type: object + type: array + volumeMounts: + items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: array + workingDir: + type: string + required: + - name + type: object + type: array + slaveHA: + description: Slave high availability mechanism configuration. + properties: + slaveHAGracePeriod: + description: Time in seconds between when a node fails, and when + slave high availability mechanism starts relocating shards. If + set to 0, will not affect cluster configuration. + format: int32 + type: integer + type: object + uiAnnotations: + additionalProperties: + type: string + description: Annotations for Redis Enterprise UI service + type: object + uiServiceType: + description: Type of service used to expose Redis Enterprise UI (https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types) + enum: + - ClusterIP + - NodePort + - LoadBalancer + - ExternalName + type: string + upgradeSpec: + description: Specification for upgrades of Redis Enterprise + properties: + autoUpgradeRedisEnterprise: + description: Whether to upgrade Redis Enterprise automatically when + operator is upgraded + type: boolean + required: + - autoUpgradeRedisEnterprise + type: object + username: + description: Username for the admin user of Redis Enterprise + type: string + volumes: + description: additional volumes + items: + description: 'Volume represents a named volume in a pod that may be + accessed by any container in the pod. + More info: https://kubernetes.io/docs/concepts/storage/volumes' + properties: + awsElasticBlockStore: + properties: + fsType: + type: string + partition: + format: int32 + type: integer + readOnly: + type: boolean + volumeID: + type: string + required: + - volumeID + type: object + azureDisk: + properties: + cachingMode: + type: string + diskName: + type: string + diskURI: + type: string + fsType: + type: string + kind: + type: string + readOnly: + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + properties: + readOnly: + type: boolean + secretName: + type: string + shareName: + type: string + required: + - secretName + - shareName + type: object + cephfs: + properties: + monitors: + items: + type: string + type: array + path: + type: string + readOnly: + type: boolean + secretFile: + type: string + secretRef: + properties: + name: + type: string + type: object + user: + type: string + required: + - monitors + type: object + cinder: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + volumeID: + type: string + required: + - volumeID + type: object + configMap: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + csi: + properties: + driver: + type: string + fsType: + type: string + nodePublishSecretRef: + properties: + name: + type: string + type: object + readOnly: + type: boolean + volumeAttributes: + additionalProperties: + type: string + type: object + required: + - driver + type: object + downwardAPI: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + 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 + resource: + type: string + required: + - resource + type: object + required: + - path + type: object + type: array + type: object + emptyDir: + properties: + medium: + type: string + sizeLimit: + 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 + type: object + fc: + properties: + fsType: + type: string + lun: + format: int32 + type: integer + readOnly: + type: boolean + targetWWNs: + items: + type: string + type: array + wwids: + items: + type: string + type: array + type: object + flexVolume: + properties: + driver: + type: string + fsType: + type: string + options: + additionalProperties: + type: string + type: object + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + required: + - driver + type: object + flocker: + properties: + datasetName: + type: string + datasetUUID: + type: string + type: object + gcePersistentDisk: + properties: + fsType: + type: string + partition: + format: int32 + type: integer + pdName: + type: string + readOnly: + type: boolean + required: + - pdName + type: object + gitRepo: + properties: + directory: + type: string + repository: + type: string + revision: + type: string + required: + - repository + type: object + glusterfs: + properties: + endpoints: + type: string + path: + type: string + readOnly: + type: boolean + required: + - endpoints + - path + type: object + hostPath: + properties: + path: + type: string + type: + type: string + required: + - path + type: object + iscsi: + properties: + chapAuthDiscovery: + type: boolean + chapAuthSession: + type: boolean + fsType: + type: string + initiatorName: + type: string + iqn: + type: string + iscsiInterface: + type: string + lun: + format: int32 + type: integer + portals: + items: + type: string + type: array + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + targetPortal: + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + type: string + nfs: + properties: + path: + type: string + readOnly: + type: boolean + server: + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + properties: + claimName: + type: string + readOnly: + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + properties: + fsType: + type: string + pdID: + type: string + required: + - pdID + type: object + portworxVolume: + properties: + fsType: + type: string + readOnly: + type: boolean + volumeID: + type: string + required: + - volumeID + type: object + projected: + properties: + defaultMode: + format: int32 + type: integer + sources: + items: + properties: + configMap: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + downwardAPI: + properties: + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + 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 + resource: + type: string + required: + - resource + type: object + required: + - path + type: object + type: array + type: object + secret: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + serviceAccountToken: + properties: + audience: + type: string + expirationSeconds: + format: int64 + type: integer + path: + type: string + required: + - path + type: object + type: object + type: array + required: + - sources + type: object + quobyte: + properties: + group: + type: string + readOnly: + type: boolean + registry: + type: string + tenant: + type: string + user: + type: string + volume: + type: string + required: + - registry + - volume + type: object + rbd: + properties: + fsType: + type: string + image: + type: string + keyring: + type: string + monitors: + items: + type: string + type: array + pool: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + user: + type: string + required: + - image + - monitors + type: object + scaleIO: + properties: + fsType: + type: string + gateway: + type: string + protectionDomain: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + sslEnabled: + type: boolean + storageMode: + type: string + storagePool: + type: string + system: + type: string + volumeName: + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + optional: + type: boolean + secretName: + type: string + type: object + storageos: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + volumeName: + type: string + volumeNamespace: + type: string + type: object + vsphereVolume: + properties: + fsType: + type: string + storagePolicyID: + type: string + storagePolicyName: + type: string + volumePath: + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + type: object + type: object + diff --git a/chart/redis-operator/files/crd/redb_crd.yaml b/chart/redis-operator/files/crd/redb_crd.yaml new file mode 100644 index 0000000..6965ad4 --- /dev/null +++ b/chart/redis-operator/files/crd/redb_crd.yaml @@ -0,0 +1,550 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: redisenterprisedatabases.app.redislabs.com +spec: + group: app.redislabs.com + names: + kind: RedisEnterpriseDatabase + listKind: RedisEnterpriseDatabaseList + plural: redisenterprisedatabases + singular: redisenterprisedatabase + shortNames: + - redb + scope: Namespaced + subresources: + status: {} + version: v1alpha1 + versions: + - name: v1alpha1 + served: true + storage: true + validation: + openAPIV3Schema: + description: RedisEnterpriseDatabase is the Schema for the redisenterprisedatabases + API + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + description: RedisEnterpriseDatabaseSpec defines the desired state of RedisEnterpriseDatabase + properties: + alertSettings: + description: Settings for database alerts + properties: + bdb_backup_delayed: + description: "Periodic backup has been delayed for longer than specified threshold value [minutes]. + -Note threshold is commented (allow string/int/float and support backwards compatibility) but is required" + properties: + enabled: + description: Alert enabled or disabled + type: boolean +# threshold: +# description: Threshold for alert going on/off +# type: string + x-kubernetes-preserve-unknown-fields: true + bdb_crdt_src_high_syncer_lag: + description: "Active-active source - sync lag is higher than specified threshold value [seconds] + -Note threshold is commented (allow string/int/float and support backwards compatibility) but is required" + properties: + enabled: + description: Alert enabled or disabled + type: boolean +# threshold: +# description: Threshold for alert going on/off +# type: string + x-kubernetes-preserve-unknown-fields: true + bdb_crdt_src_syncer_connection_error: + description: "Active-active source - sync has connection error while trying to connect replica source + -Note threshold is commented (allow string/int/float and support backwards compatibility) but is required" + properties: + enabled: + description: Alert enabled or disabled + type: boolean +# threshold: +# description: Threshold for alert going on/off +# type: string + x-kubernetes-preserve-unknown-fields: true + bdb_crdt_src_syncer_general_error: + description: "Active-active source - sync encountered in general error + -Note threshold is commented (allow string/int/float and support backwards compatibility) but is required" + properties: + enabled: + description: Alert enabled or disabled + type: boolean +# threshold: +# description: Threshold for alert going on/off +# type: string + x-kubernetes-preserve-unknown-fields: true + bdb_high_latency: + description: "Latency is higher than specified threshold value [micro-sec] + -Note threshold is commented (allow string/int/float and support backwards compatibility) but is required" + properties: + enabled: + description: Alert enabled or disabled + type: boolean +# threshold: +# description: Threshold for alert going on/off +# type: string + x-kubernetes-preserve-unknown-fields: true + bdb_high_throughput: + description: "Throughput is higher than specified threshold value [requests / sec.] + -Note threshold is commented (allow string/int/float and support backwards compatibility) but is required" + properties: + enabled: + description: Alert enabled or disabled + type: boolean +# threshold: +# description: Threshold for alert going on/off +# type: string + x-kubernetes-preserve-unknown-fields: true + bdb_long_running_action: + description: "An alert for state-machines that are running for too long + -Note threshold is commented (allow string/int/float and support backwards compatibility) but is required" + properties: + enabled: + description: Alert enabled or disabled + type: boolean +# threshold: +# description: Threshold for alert going on/off +# type: string + x-kubernetes-preserve-unknown-fields: true + bdb_low_throughput: + description: "Throughput is lower than specified threshold value [requests / sec.] + -Note threshold is commented (allow string/int/float and support backwards compatibility) but is required" + properties: + enabled: + description: Alert enabled or disabled + type: boolean +# threshold: +# description: Threshold for alert going on/off +# type: string + x-kubernetes-preserve-unknown-fields: true + bdb_ram_dataset_overhead: + description: "Dataset RAM overhead of a shard has reached the threshold value [% of its RAM limit] + -Note threshold is commented (allow string/int/float and support backwards compatibility) but is required" + properties: + enabled: + description: Alert enabled or disabled + type: boolean +# threshold: +# description: Threshold for alert going on/off +# type: string + x-kubernetes-preserve-unknown-fields: true + bdb_ram_values: + description: "Percent of values kept in a shard's RAM is lower than [% of its key count] + -Note threshold is commented (allow string/int/float and support backwards compatibility) but is required" + properties: + enabled: + description: Alert enabled or disabled + type: boolean +# threshold: +# description: Threshold for alert going on/off +# type: string + x-kubernetes-preserve-unknown-fields: true + bdb_replica_src_high_syncer_lag: + description: "Replica-of source - sync lag is higher than specified threshold value [seconds] + -Note threshold is commented (allow string/int/float and support backwards compatibility) but is required" + properties: + enabled: + description: Alert enabled or disabled + type: boolean +# threshold: +# description: Threshold for alert going on/off +# type: string + x-kubernetes-preserve-unknown-fields: true + bdb_replica_src_syncer_connection_error: + description: "Replica-of source - sync has connection error while trying to connect replica source + -Note threshold is commented (allow string/int/float and support backwards compatibility) but is required" + properties: + enabled: + description: Alert enabled or disabled + type: boolean +# threshold: +# description: Threshold for alert going on/off +# type: string + x-kubernetes-preserve-unknown-fields: true + bdb_shard_num_ram_values: + description: "Number of values kept in a shard's RAM is lower than [values] + -Note threshold is commented (allow string/int/float and support backwards compatibility) but is required" + properties: + enabled: + description: Alert enabled or disabled + type: boolean +# threshold: +# description: Threshold for alert going on/off +# type: string + x-kubernetes-preserve-unknown-fields: true + bdb_size: + description: "Dataset size has reached the threshold value [% of the memory limit] expected fields: + -Note threshold is commented (allow string/int/float and support backwards compatibility) but is required" + properties: + enabled: + description: Alert enabled or disabled + type: boolean +# threshold: +# description: Threshold for alert going on/off +# type: string + x-kubernetes-preserve-unknown-fields: true + type: object + backup: + description: Target for automatic database backups. + properties: + abs: + properties: + absSecretName: + description: The name of the K8s secret that holds ABS credentials. + The secret must contain the keys "AccountName" and "AccountKey", + and these must hold the corresponding credentials + type: string + container: + description: Azure Blob Storage container name. + type: string + subdir: + description: Optional. Azure Blob Storage subdir under container. + type: string + required: + - absSecretName + - container + type: object + ftp: + properties: + url: + description: "a URI of the ftps://[USER[:PASSWORD]@]HOST[:PORT]/PATH[/]" + type: string + pattern: ftps?://(([^@]+)@)?([^@/:]+)(:(\d+))?([/\.]/?[^@/\.]+)*?/?$ + required: + - url + type: object + gcs: + description: GoogleStorage + properties: + bucketName: + description: Google Storage bucket name. + type: string + gcsSecretName: + description: The name of the K8s secret that holds the Google + Cloud Storage credentials. The secret must contain the keys + "CLIENT_ID", "PRIVATE_KEY", "PRIVATE_KEY_ID", "CLIENT_EMAIL" + and these must hold the corresponding credentials. The keys + should correspond to the values in the key JSON. + type: string + subdir: + description: Optional. Google Storage subdir under bucket. + type: string + required: + - bucketName + - gcsSecretName + type: object + interval: + description: Backup Interval in seconds + type: integer + mount: + description: MountPointStorage + properties: + path: + description: Path to the local mount point. You must create + the mount point on all nodes, and the redislabs:redislabs + user must have read and write permissions on the local mount + point. + type: string + required: + - path + type: object + s3: + properties: + awsSecretName: + description: The name of the K8s secret that holds the AWS credentials. + The secret must contain the keys "AWS_ACCESS_KEY_ID" and "AWS_SECRET_ACCESS_KEY", + and these must hold the corresponding credentials. + type: string + bucketName: + description: Amazon S3 bucket name. + type: string + subdir: + description: Optional. Amazon S3 subdir under bucket. + type: string + required: + - awsSecretName + - bucketName + type: object + sftp: + properties: + sftp_url: + description: SFTP url + type: string + pattern: ^sftp://(([^@]+)@)?([^@/:]+)(:(\d+))?(/([^@/\.]+[/\.]?)*)?$ + sftpSecretName: + description: The name of the K8s secret that holds SFTP credentials. + The secret must contain the "Key" key, which is the SSH private + key for connecting to the sftp server. + type: string + required: + - sftpSecretName + - sftp_url + type: object + swift: + properties: + auth_url: + description: Swift service authentication URL. + type: string + pattern: ^https?://(([^@]+)@)?([^@/:]+)(:(\d+))?([/\.]([^@/\.]+))*?/?$ + container: + description: Swift object store container for storing the backup + files. + type: string + prefix: + description: Optional. Prefix (path) of backup files in the + swift container. + type: string + swiftSecretName: + description: 'The name of the K8s secret that holds Swift credentials. + The secret must contain the keys "Key" and "User", and these + must hold the corresponding credentials: service access key + and service user name (pattern for the latter does not allow + special characters &,<,>,")' + type: string + required: + - auth_url + - container + - swiftSecretName + type: object + type: object + clientAuthenticationCertificates: + description: The Secrets containing TLS Client Certificate to use for + Authentication + items: + type: string + type: array + databaseSecretName: + description: The name of the K8s secret that holds the password to the + database. + type: string + defaultUser: + description: Is connecting with a default user allowed? + type: boolean + evictionPolicy: + description: Database eviction policy. see more https://docs.redislabs.com/latest/rs/administering/database-operations/eviction-policy/ + type: string + memorySize: + description: memory size of database. use formats like 100MB, 0.1GB. + minimum value in 100MB. + type: string + modulesList: + description: List of modules associated with database + items: + description: 'Redis Enterprise Module: https://redislabs.com/redis-enterprise/modules/' + properties: + config: + description: Module command line arguments e.g. VKEY_MAX_ENTITY_COUNT + 30 + type: string + name: + description: The module's name e.g "ft" for redissearch + type: string + version: + description: Module's semantic version e.g "1.6.12" + type: string + required: + - name + - version + type: object + type: array + persistence: + description: Database on-disk persistence policy + enum: + - disabled + - aofEverySecond + - aofAlways + - snapshotEvery1Hour + - snapshotEvery6Hour + - snapshotEvery12Hour + type: string + rackAware: + description: 'Whether database should be rack aware. This improves availability + - more information: https://docs.redislabs.com/latest/rs/concepts/high-availability/rack-zone-awareness/' + type: boolean + redisEnterpriseCluster: + description: Connection to Redis Enterprise Cluster + properties: + name: + description: The name of the Redis Enterprise Cluster where the + database should be stored. + type: string + required: + - name + type: object + replicaSources: + description: What databases to replicate from + items: + properties: + clientKeySecret: + description: Secret that defines what client key to use. The + secret needs 2 keys in it's map, "cert" that is the PEM encoded + certificate and "key" that is the PEM encoded private key + type: string + compression: + description: GZIP Compression level (0-9) to use for replication + type: integer + replicaSourceName: + description: Kubernetes resource name of type ReplicaSourceType + type: string + replicaSourceType: + description: Determines what Kuberetes resource ReplicaSourceName + refers to SECRET - Get URI from secret named in ReplicaSourceName. The + secret will have a uri key that defines the complete, redis:// + URI REDB - Determine URI from Kubernetes REDB resource named + in ReplicaSourceName + type: string + serverCertSecret: + description: Secret that defines the Server's certificate. The + secret needs 1 key in it's map, "cert" that is the PEM encoded + certificate + type: string + tlsSniName: + description: TLS SNI Name to use + type: string + required: + - replicaSourceName + - replicaSourceType + type: object + type: array + replication: + description: In-memory database replication. When enabled, database + will have replica shard for every master - leading to higher availability. + type: boolean + rolesPermissions: + description: List of Redis Enteprise ACL and Role bindings to apply + items: + description: Redis Enterprise Role and ACL Binding + properties: + acl: + description: Acl Name of RolePermissionType + type: string + role: + description: Role Name of RolePermissionType + type: string + type: + description: Type of Redis Enterprise Database Role Permission + type: string + required: + - acl + - role + - type + type: object + type: array + shardCount: + description: Number of database server-side shards + type: integer + tlsMode: + description: Require SSL authenticated and encrypted connections to + the database. enabled - all incoming connections to the Database must + use SSL. disabled - no incoming connection to the Database should + use SSL. replica_ssl - databases that replicate from this one need + to use SSL. + enum: + - disabled + - enabled + - replica_ssl + type: string + type: object + status: + description: RedisEnterpriseDatabaseStatus defines the observed state of + RedisEnterpriseDatabase + properties: + createdTime: + description: Time when the database was created + type: string + databaseUID: + description: Database UID provided by redis enterprise + type: string + internalEndpoints: + description: Endpoints listed internally by the Redis Enterprise Cluster. + Can be used to correlate a ReplicaSourceStatus entry. + items: + properties: + host: + description: Hostname assigned to the database + type: string + port: + description: Database port name + type: integer + type: object + type: array + lastActionStatus: + description: Status of the last action done by operator on this database + type: string + lastActionUid: + description: UID of the last action done by operator on this database + type: string + lastUpdated: + description: Time when the database was last updated + type: string + observedGeneration: + description: 'The generation (built in update counter of K8s) of the + REDB resource that was fully acted upon, meaning that all changes + were handled and sent as an API call to the Redis Enterprise Cluster + (REC). This field value should equal the current generation when the + resource changes were handled. Note: the lastActionStatus field tracks + actions handled asynchronously by the Redis Enterprise Cluster.' + format: int64 + type: integer + redisEnterpriseCluster: + description: The Redis Enterprise Cluster Object this Resource is associated + with + type: string + replicaSourceStatuses: + description: ReplicaSource statuses + items: + properties: + endpointHost: + description: The internal host name of the replica source database. + Can be used as an identifier. See the internalEndpoints list + on the REDB status. + type: string + lag: + description: Lag in millisec between source and destination (while + synced). + type: integer + lastError: + description: Last error encountered when syncing from the source. + type: string + lastUpdate: + description: Time when we last receive an update from the source. + type: string + rdbSize: + description: The source’s RDB size to be transferred during the + syncing phase. + type: integer + rdbTransferred: + description: Number of bytes transferred from the source’s RDB + during the syncing phase. + type: integer + status: + description: Sync status of this source + type: string + required: + - endpointHost + type: object + type: array + shardStatuses: + additionalProperties: + type: integer + description: Aggregated statuses of shards + type: object + specStatus: + description: Whether the desired specification is valid + type: string + status: + description: The status of the database + type: string + version: + description: Database compatibility version + type: string + type: object + type: object + diff --git a/redis/chart/redis-operator/logo.png b/chart/redis-operator/logo.png similarity index 100% rename from redis/chart/redis-operator/logo.png rename to chart/redis-operator/logo.png diff --git a/redis/chart/redis-operator/templates/_helpers.tpl b/chart/redis-operator/templates/_helpers.tpl similarity index 69% rename from redis/chart/redis-operator/templates/_helpers.tpl rename to chart/redis-operator/templates/_helpers.tpl index 07f868a..3d00dfe 100644 --- a/redis/chart/redis-operator/templates/_helpers.tpl +++ b/chart/redis-operator/templates/_helpers.tpl @@ -6,6 +6,18 @@ {{- printf "%s-crd-job" .Release.Name | trunc 63 -}} {{- end -}} +{{- define "redis_operator.CRsConfigMap" -}} +{{- printf "%s-cr-config-map" .Release.Name | trunc 63 -}} +{{- end -}} + +{{- define "redis_operator.UBBAgentConfigMap" -}} +{{- printf "%s-ubbagent-config-map" .Release.Name | trunc 63 -}} +{{- end -}} + +{{- define "redis_operator.CRsJob" -}} +{{- printf "%s-cr-job" .Release.Name | trunc 63 -}} +{{- end -}} + {{- define "redis_operator.DeploymentName" -}} {{- printf "%s-redis-operator" .Release.Name | trunc 63 -}} {{- end -}} diff --git a/redis/chart/redis-operator/templates/application.yaml b/chart/redis-operator/templates/application.yaml similarity index 100% rename from redis/chart/redis-operator/templates/application.yaml rename to chart/redis-operator/templates/application.yaml diff --git a/chart/redis-operator/templates/configmap/cr.yaml b/chart/redis-operator/templates/configmap/cr.yaml new file mode 100644 index 0000000..98bf8e5 --- /dev/null +++ b/chart/redis-operator/templates/configmap/cr.yaml @@ -0,0 +1,32 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "redis_operator.CRsConfigMap" . }} + labels: + app.kubernetes.io/name: {{ .Release.Name }} + app.kubernetes.io/component: cr-configmap +data: + redis-enterprise-cluster.yaml: |- + apiVersion: "app.redislabs.com/v1alpha1" + kind: "RedisEnterpriseCluster" + metadata: + name: "redis-enterprise" + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: "{{ .Release.Name }}" + spec: + nodes: {{ .Values.operator.replicas }} + persistentSpec: + enabled: true + storageClassName: "standard" + uiServiceType: "{{ if .Values.ingressAvailable -}}LoadBalancer{{- else -}}ClusterIP{{- end }}" + username: "{{ .Values.operator.redisAdmin }}" + redisEnterpriseNodeResources: + limits: + cpu: "{{ .Values.operator.nodeCpu }}m" + memory: "{{ .Values.operator.nodeMem }}Gi" + requests: + cpu: "{{ .Values.operator.nodeCpu }}m" + memory: "{{ .Values.operator.nodeMem }}Gi" + + diff --git a/redis/chart/redis-operator/templates/configmap/crd.yaml b/chart/redis-operator/templates/configmap/crd.yaml similarity index 100% rename from redis/chart/redis-operator/templates/configmap/crd.yaml rename to chart/redis-operator/templates/configmap/crd.yaml diff --git a/chart/redis-operator/templates/configmap/ubbagent.yaml b/chart/redis-operator/templates/configmap/ubbagent.yaml new file mode 100644 index 0000000..d1b072d --- /dev/null +++ b/chart/redis-operator/templates/configmap/ubbagent.yaml @@ -0,0 +1,37 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "redis_operator.UBBAgentConfigMap" . }} + labels: + app.kubernetes.io/name: {{ .Release.Name }} + app.kubernetes.io/component: ubbagent-configmap +data: + config.yaml: |- + identities: + - name: gcp + gcp: + # A base64-encoded service account key used to report usage to + # Google Service Control. + encodedServiceAccountKey: $AGENT_ENCODED_KEY + metrics: + - name: shards_used + type: int + endpoints: + - name: servicecontrol + aggregation: + bufferSeconds: 3600 + endpoints: + - name: servicecontrol + servicecontrol: + identity: gcp + serviceName: redislabs.mp-redislabs-public.appspot.com + consumerId: $AGENT_CONSUMER_ID + sources: + - name: redislabs.mp-redislabs-public.appspot.com + heartbeat: + metric: shards_used + intervalSeconds: 600 + value: + int64Value: 600 + labels: + auto: true \ No newline at end of file diff --git a/chart/redis-operator/templates/deployment/operator.yaml b/chart/redis-operator/templates/deployment/operator.yaml new file mode 100644 index 0000000..1f3c807 --- /dev/null +++ b/chart/redis-operator/templates/deployment/operator.yaml @@ -0,0 +1,90 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis-enterprise-operator + namespace: {{ .Release.Namespace }} +spec: + replicas: 1 + selector: + matchLabels: + name: redis-enterprise-operator + template: + metadata: + labels: + name: redis-enterprise-operator + spec: + serviceAccountName: {{ .Values.operator.serviceAccountName }} + initContainers: + {{- include "initContainerWaitForCRDsDeploy" . | nindent 6 }} + containers: + - name: redis-enterprise-operator + image: {{ .Values.operator.image.repository }}/operator:{{ .Values.operator.image.tag }} + command: + - redis-enterprise-operator + imagePullPolicy: Always + env: + - name: WATCH_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: OPERATOR_NAME + value: redis-enterprise-operator + - name: DATABASE_CONTROLLER_ENABLED + value: "false" + resources: + limits: + cpu: 4000m + memory: 512Mi + requests: + cpu: 500m + memory: 256Mi + - name: usage-meter + image: {{ .Values.usagemeter.image }} + imagePullPolicy: Always + env: + - name: INTERVAL + value: "60" + - name: ENDPOINT + value: "http://localhost:3456" + - name: METRICS + value: "--shards --report-value shards_used" + - name: billing-agent + image: {{ .Values.billingagent.image }} + imagePullPolicy: Always + - name: ubbagent + image: gcr.io/cloud-marketplace-tools/metering/ubbagent + imagePullPolicy: IfNotPresent + env: + - name: NODE_CPU + value: "{{ .Values.operator.nodeCpu }}m" + - name: NODE_MEM + value: "{{ .Values.operator.nodeMem }}Gi" + - name: AGENT_CONFIG_FILE + value: /ubbagent/config.yaml + - name: AGENT_LOCAL_PORT + value: "6080" + - name: AGENT_STATE_DIR + value: /opt/persistent/ubbagent + - name: AGENT_REPORT_DIR + value: /opt/persistent/ubbagent/reports + - name: AGENT_ENCODED_KEY + valueFrom: + secretKeyRef: + name: "{{ .Values.reportingSecret }}" + key: reporting-key + - name: AGENT_CONSUMER_ID + valueFrom: + secretKeyRef: + name: "{{ .Values.reportingSecret }}" + key: consumer-id + volumeMounts: + - name: ubb-configmap + mountPath: /ubbagent/ + volumes: + - name: ubb-configmap + configMap: + name: {{ template "redis_operator.UBBAgentConfigMap" . }} \ No newline at end of file diff --git a/chart/redis-operator/templates/job/cr-create.yaml b/chart/redis-operator/templates/job/cr-create.yaml new file mode 100644 index 0000000..6495a39 --- /dev/null +++ b/chart/redis-operator/templates/job/cr-create.yaml @@ -0,0 +1,38 @@ +apiVersion: batch/v1 +kind: Job +metadata: + annotations: + name: {{ template "redis_operator.CRsJob" . }} + labels: + app.kubernetes.io/name: "{{ .Release.Name }}" + app.kubernetes.io/component: cr-job +spec: + ttlSecondsAfterFinished: 300 + backoffLimit: 3 + completions: 1 + parallelism: 1 + template: + spec: + initContainers: + {{- include "initContainerWaitForCRDsDeploy" . | nindent 6 }} + containers: + - command: + - "/bin/bash" + - "-ec" + - | + for cr in /cr_to_create/*; + do cat $cr; kubectl apply -f $cr; + done + image: {{ .Values.deployerHelm.image }} + imagePullPolicy: Always + name: cr-create + volumeMounts: + - name: cr-configmap + mountPath: /cr_to_create/ + dnsPolicy: ClusterFirst + restartPolicy: OnFailure + serviceAccountName: {{ .Values.CRJobServiceAccount }} + volumes: + - name: cr-configmap + configMap: + name: {{ template "redis_operator.CRsConfigMap" . }} diff --git a/redis/chart/redis-operator/templates/job/crd-create.yaml b/chart/redis-operator/templates/job/crd-create.yaml similarity index 100% rename from redis/chart/redis-operator/templates/job/crd-create.yaml rename to chart/redis-operator/templates/job/crd-create.yaml diff --git a/chart/redis-operator/values.yaml b/chart/redis-operator/values.yaml new file mode 100644 index 0000000..214aaad --- /dev/null +++ b/chart/redis-operator/values.yaml @@ -0,0 +1,11 @@ +operator: + serviceAccountName: null + image: + repository: null + tag: null +ubbagent: + image: null +billingagent: + image: us-central1-docker.pkg.dev/sam-playground-290106/redis-mp/billing-agent:6.0.12-5 +usagemeter: + image: us-central1-docker.pkg.dev/sam-playground-290106/redis-mp/usage-meter:6.0.12-5 diff --git a/common.Makefile b/common.Makefile deleted file mode 100644 index b8c1c8e..0000000 --- a/common.Makefile +++ /dev/null @@ -1,43 +0,0 @@ -ifndef __COMMON_MAKEFILE__ - -__COMMON_MAKEFILE__ := included - - -define print_target - @$(call print_notice,Building $@...) -endef - - -define print_notice - printf "\n\033[93m\033[1m$(1)\033[0m\n" -endef - - -define print_error - printf "\n\033[93m\033[1m$(1)\033[0m\n" -endef - - -# MARKETPLACE_TOOLS_TAG is the tag of the container images published -# by marketplace-k8s-app-tools. -tag_from_file := $(shell cat "$(dir $(realpath $(lastword $(MAKEFILE_LIST))))/MARKETPLACE_TOOLS_TAG") -MARKETPLACE_TOOLS_TAG ?= $(tag_from_file) -export MARKETPLACE_TOOLS_TAG - -$(info ---- MARKETPLACE_TOOLS_TAG = $(MARKETPLACE_TOOLS_TAG)) - - -.build: - mkdir -p "$@" - - -.build/tmp: | .build - mkdir -p "$@" - - -.PHONY: clean -clean:: - rm -Rf .build - - -endif diff --git a/crd.Makefile b/crd.Makefile deleted file mode 100644 index 780966c..0000000 --- a/crd.Makefile +++ /dev/null @@ -1,20 +0,0 @@ -ifndef __CRD_MAKEFILE__ - -__CRD_MAKEFILE__ := included - - -# Installs the application CRD on the cluster. -.PHONY: crd/install -crd/install: - kubectl apply -f "https://raw.githubusercontent.com/GoogleCloudPlatform/marketplace-k8s-app-tools/master/crd/app-crd.yaml" - kubectl apply -f "https://raw.githubusercontent.com/GoogleCloudPlatform/marketplace-k8s-app-tools/master/crd/kalm.yaml" - - -# Uninstalls the application CRD from the cluster. -.PHONY: crd/uninstall -crd/uninstall: - kubectl delete -f "https://raw.githubusercontent.com/GoogleCloudPlatform/marketplace-k8s-app-tools/master/crd/app-crd.yaml" - kubectl delete -f "https://raw.githubusercontent.com/GoogleCloudPlatform/marketplace-k8s-app-tools/master/crd/kalm.yaml" - - -endif diff --git a/redis/deployer/Dockerfile b/deployer/Dockerfile similarity index 95% rename from redis/deployer/Dockerfile rename to deployer/Dockerfile index 7858887..29571c6 100644 --- a/redis/deployer/Dockerfile +++ b/deployer/Dockerfile @@ -5,7 +5,7 @@ FROM marketplace.gcr.io/google/debian9 AS build ARG CHART_NAME RUN apt-get update \ - && apt-get install -y --no-install-recommends gettext + && apt-get install -y --no-install-recommends gettext jq ADD chart/$CHART_NAME /tmp/chart RUN cd /tmp && tar -czvf /tmp/$CHART_NAME.tar.gz chart diff --git a/gcloud.Makefile b/gcloud.Makefile deleted file mode 100644 index 920e28f..0000000 --- a/gcloud.Makefile +++ /dev/null @@ -1,38 +0,0 @@ -ifndef __GCLOUD_MAKEFILE__ - -__GCLOUD_MAKEFILE__ := included - -# Include this Makefile to automatically derive registry -# and target namespace from the current gcloud and kubectl -# configurations. -# This is for convenience over having to specifying the -# environment variables manually. - - -ifndef REGISTRY - # We replace ':' with '/' characters to support a now-deprecated - # projects format "google.com:my-project". - REGISTRY := gcr.io/$(shell gcloud config get-value project | tr ':' '/') -endif - -ifndef GCS_URL - # We replace ':' with '_' characters to support a now-deprecated - # projects format "google.com:my-project". - # Note that a project does not have any bucket by default. - # This is just a conventional default value, where the user is expected - # to create GCS bucket with the same name as the project - GCS_URL := gs://$(shell gcloud config get-value project | tr ':' '_') -endif - -ifndef NAMESPACE - NAMESPACE := $(shell kubectl config view -o jsonpath="{.contexts[?(@.name==\"$$(kubectl config current-context)\")].context.namespace}") - ifeq ($(NAMESPACE),) - NAMESPACE = default - endif -endif - -$(info ---- REGISTRY = $(REGISTRY)) -$(info ---- GCS_URL = $(GCS_URL)) -$(info ---- NAMESPACE = $(NAMESPACE)) - -endif diff --git a/images.Makefile b/images.Makefile deleted file mode 100644 index 2cb4494..0000000 --- a/images.Makefile +++ /dev/null @@ -1,10 +0,0 @@ -# Gets first argv as image to resolve full path of image with sha256 hash sum. -get_sha256 = $(shell docker pull $1 2>&1 > /dev/null \ -&& docker inspect --format='{{ index .RepoDigests 0 }}' $1) - -# Gets first argv as image and second as variable to get from container. -get_var_from_container = $(shell docker pull $1 2>&1 > /dev/null \ -&& docker run --rm --entrypoint=printenv $1 $2) - -# Gets first argv as image and try to get C2D_RELEASE from container. -get_c2d_release = $(call get_var_from_container, $1, C2D_RELEASE) diff --git a/redis/Makefile b/redis/Makefile deleted file mode 100644 index 49522c2..0000000 --- a/redis/Makefile +++ /dev/null @@ -1,58 +0,0 @@ -include ../crd.Makefile -include ../gcloud.Makefile -include ../var.Makefile -include ../images.Makefile - -VERIFY_WAIT_TIMEOUT = 1200 - -CHART_NAME := redis-operator -APP_ID ?= $(CHART_NAME) - -#SOURCE_REGISTRY ?= marketplace.gcr.io/google -PROJECT=joshua-playground -SOURCE_REGISTRY ?= gcr.io -TRACK ?= 1.20 - - -IMAGE_MAIN ?= $(SOURCE_REGISTRY)/$(PROJECT)/redislabs:$(TRACK) -IMAGE_DEPLOYER_HELM ?= gcr.io/cloud-marketplace-tools/k8s/deployer_helm:$(MARKETPLACE_TOOLS_TAG) - - -# Main image -image-$(CHART_NAME) := $(call get_sha256,$(IMAGE_MAIN)) - -# List of images used in application -ADDITIONAL_IMAGES := deployer-helm - -# Additional images variable names should correspond with ADDITIONAL_IMAGES list -# Should be dynamically to use $(MARKETPLACE_TOOLS_TAG) -image-deployer-helm ?= $(call get_sha256,$(IMAGE_DEPLOYER_HELM)) - -#C2D_CONTAINER_RELEASE := $(call get_c2d_release,$(image-$(CHART_NAME))) -C2D_CONTAINER_RELEASE ?= $(TRACK).1 - -BUILD_ID := $(shell date -u +%Y%m%d-%H%M%S) -RELEASE ?= $(C2D_CONTAINER_RELEASE)-$(BUILD_ID) - -$(info ---- C2D_CONTAINER_RELEASE = $(C2D_CONTAINER_RELEASE)) -$(info ---- TRACK = $(TRACK)) -$(info ---- RELEASE = $(RELEASE)) -$(info ---- SOURCE_REGISTRY = $(SOURCE_REGISTRY)) - -APP_DEPLOYER_IMAGE ?= $(REGISTRY)/$(APP_ID)/deployer:$(RELEASE) -APP_DEPLOYER_IMAGE_TRACK_TAG ?= $(REGISTRY)/$(APP_ID)/deployer:$(TRACK) -APP_GCS_PATH ?= $(GCS_URL)/$(APP_ID)/$(TRACK) - -NAME ?= $(APP_ID)-1 - -APP_PARAMETERS ?= { \ - "name": "$(NAME)", \ - "namespace": "$(NAMESPACE)" \ -} - -# app_v2.Makefile provides the main targets for installing the application. -# It requires several APP_* variables defined above, and thus must be included after. -include ../c2d_deployer.Makefile - -# Build tester image -app/build:: .build/$(CHART_NAME)/tester diff --git a/redis/chart/redis-operator/files/crd/redislabs.com_redisenterpriseclusters.app_crd.yaml b/redis/chart/redis-operator/files/crd/redislabs.com_redisenterpriseclusters.app_crd.yaml deleted file mode 100644 index d5e26f4..0000000 --- a/redis/chart/redis-operator/files/crd/redislabs.com_redisenterpriseclusters.app_crd.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: redisenterpriseclusters.app.redislabs.com -spec: - group: app.redislabs.com - names: - kind: RedisEnterpriseCluster - listKind: RedisEnterpriseClusterList - plural: redisenterpriseclusters - singular: redisenterprisecluster - shortNames: - - rec - scope: Namespaced - version: v1alpha1 \ No newline at end of file diff --git a/redis/chart/redis-operator/templates/deployment/operator.yaml b/redis/chart/redis-operator/templates/deployment/operator.yaml deleted file mode 100644 index ad566d8..0000000 --- a/redis/chart/redis-operator/templates/deployment/operator.yaml +++ /dev/null @@ -1,43 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ template "redis_operator.DeploymentName" . }} - namespace: {{ .Release.Namespace }} # In Helm Jaeger, this is missing - labels: - app.kubernetes.io/name: "{{ .Release.Name }}" - app.kubernetes.io/component: operator -spec: - replicas: 1 - selector: - matchLabels: - name: {{ template "redis_operator.DeploymentName" . }} - template: - metadata: - labels: - name: {{ template "redis_operator.DeploymentName" . }} - app.kubernetes.io/name: "{{ .Release.Name }}" - app.kubernetes.io/component: operator - spec: - serviceAccountName: {{ .Values.operator.serviceAccountName }} - initContainers: - {{- include "initContainerWaitForCRDsDeploy" . | nindent 6 }} - containers: - - name: redis-enterprise-operator - image: {{ .Values.operator.image.repository }}:{{ .Values.operator.image.tag }} - command: - - redis-enterprise-operator - imagePullPolicy: Always - env: - - name: WATCH_NAMESPACE - # In Helm Jaeger, value is just "". But in old RedisOperator and old Jaeger, we have value from fieldRef from metadata.namespace - value: "" - - name: POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: POD_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: OPERATOR_NAME - value: "{{ template "redis_operator.DeploymentName" . }}" diff --git a/redis/chart/redis-operator/templates/rbac/rbac.yaml b/redis/chart/redis-operator/templates/rbac/rbac.yaml deleted file mode 100644 index d2c66b8..0000000 --- a/redis/chart/redis-operator/templates/rbac/rbac.yaml +++ /dev/null @@ -1,34 +0,0 @@ ---- -kind: Role -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: redis-enterprise-operator - namespace: {{ .Release.Namespace }} -rules: -- apiGroups: ["", "extensions", "apps", "rbac.authorization.k8s.io", "policy"] - resources: ["*"] - verbs: ["*"] -- apiGroups: - - app.redislabs.com - resources: ["*"] - verbs: ["*"] ---- -kind: ServiceAccount -apiVersion: v1 -metadata: - name: {{ .Values.operator.serviceAccountName }} - namespace: {{ .Release.Namespace }} ---- -kind: RoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: redis-enterprise-operator - namespace: {{ .Release.Namespace }} -subjects: -- kind: ServiceAccount - name: {{ .Values.operator.serviceAccountName }} - namespace: {{ .Release.Namespace }} -roleRef: - kind: Role - name: redis-enterprise-operator - apiGroup: rbac.authorization.k8s.io diff --git a/redis/chart/redis-operator/templates/redis-enterprise-cluster/redis-enterprise-cluster.yaml b/redis/chart/redis-operator/templates/redis-enterprise-cluster/redis-enterprise-cluster.yaml deleted file mode 100644 index fbc8be6..0000000 --- a/redis/chart/redis-operator/templates/redis-enterprise-cluster/redis-enterprise-cluster.yaml +++ /dev/null @@ -1,48 +0,0 @@ -apiVersion: "app.redislabs.com/v1alpha1" -kind: "RedisEnterpriseCluster" -metadata: - name: "redis-enterprise" - namespace: {{ .Release.Namespace }} - labels: - app.kubernetes.io/name: "{{ .Release.Name }}" -spec: - nodes: {{ .Values.operator.replicas }} - persistentSpec: - enabled: true - storageClassName: "standard" - uiServiceType: {{ if .Values.ingressAvailable -}}LoadBalancer{{- else -}}ClusterIP{{- end }} - username: {{ .Values.operator.redisAdmin }} - redisEnterpriseNodeResources: - limits: - cpu: "{{ .Values.operator.nodeCpu }}m" - memory: "{{ .Values.operator.nodeMem }}Gi" - requests: - cpu: "{{ .Values.operator.nodeCpu }}m" - memory: "{{ .Values.operator.nodeMem }}Gi" - sideContainersSpec: - - name: ubbagent - image: "{{ .Values.operator.imageUbbAgent }}" - imagePullPolicy: IfNotPresent - env: - - name: NODE_CPU - value: "{{ .Values.operator.nodeCpu }}m" - - name: NODE_MEM - value: "{{ .Values.operator.nodemem }}Gi" - - name: AGENT_CONFIG_FILE - value: /etc/ubbagent/config.yaml - - name: AGENT_LOCAL_PORT - value: "6080" - - name: AGENT_STATE_DIR - value: /opt/persistent/ubbagent - - name: AGENT_REPORT_DIR - value: /opt/persistent/ubbagent/reports - - name: AGENT_ENCODED_KEY - valueFrom: - secretKeyRef: - name: "{{ .Values.reportingSecret }}" - key: reporting-key - - name: AGENT_CONSUMER_ID - valueFrom: - secretKeyRef: - name: "{{ .Values.reportingSecret }}" - key: consumer-id diff --git a/redis/chart/redis-operator/values.yaml b/redis/chart/redis-operator/values.yaml deleted file mode 100644 index 1736329..0000000 --- a/redis/chart/redis-operator/values.yaml +++ /dev/null @@ -1,11 +0,0 @@ -operator: - serviceAccountName: null - image: - repository: null - tag: null - replicas: 3 - redisAdmin: admin@example.com - nodeCpu: 4000 - nodeMem: 15 - imageUbbAgent: gcr.io/proven-reality-226706/redislabs/ubbagent:1.15 -ingressAvailable: true # TODO remove \ No newline at end of file diff --git a/redis/resources/service-accounts.yaml b/redis/resources/service-accounts.yaml deleted file mode 100644 index 77670c7..0000000 --- a/redis/resources/service-accounts.yaml +++ /dev/null @@ -1,132 +0,0 @@ -# CRD creator service account -apiVersion: v1 -kind: ServiceAccount -metadata: - name: $CRD_SERVICE_ACCOUNT ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: $CRD_SERVICE_ACCOUNT-role -rules: -- apiGroups: - - apiextensions.k8s.io - resources: - - customresourcedefinitions - verbs: - - get - - list - - create ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: $CRD_SERVICE_ACCOUNT-rb -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: $CRD_SERVICE_ACCOUNT-role -subjects: -- kind: ServiceAccount - name: $CRD_SERVICE_ACCOUNT - namespace: $NAMESPACE ---- - -# Operator service account -apiVersion: v1 -kind: ServiceAccount -metadata: - name: $OPERATOR_SERVICE_ACCOUNT ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: $OPERATOR_SERVICE_ACCOUNT-role -rules: -- apiGroups: ["apiextensions.k8s.io"] - resources: ["customresourcedefinitions"] - verbs: ["get","list"] -- apiGroups: - - "" - resources: - - pods - - services - - endpoints - - persistentvolumeclaims - - events - - configmaps - - secrets - - serviceaccounts - verbs: - - '*' -- apiGroups: #TODO These app.redislabs.com permissions are needed, but is the full list of permissions also needed? Most come from the Jaeger operator - - app.redislabs.com - resources: ["*"] - verbs: ["*"] -- apiGroups: - - apps - resources: - - deployments - - daemonsets - - replicasets - - statefulsets - verbs: - - '*' -- apiGroups: - - monitoring.coreos.com - resources: - - servicemonitors - verbs: - - get - - create -- apiGroups: - - extensions - resources: - - replicasets - - deployments - - daemonsets - - statefulsets - - ingresses - verbs: - - '*' -- apiGroups: - - batch - resources: - - jobs - - cronjobs - verbs: - - '*' -#- apiGroups: -# - jaegertracing.io -# resources: -# - '*' -# verbs: -# - '*' -- apiGroups: - - rbac.authorization.k8s.io - resources: - - clusterrolebindings - verbs: - - '*' -- apiGroups: - - apps - - extensions - resourceNames: - - redis-operator - resources: - - deployments/finalizers - verbs: - - update ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: $OPERATOR_SERVICE_ACCOUNT-rb -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: $OPERATOR_SERVICE_ACCOUNT-role -subjects: -- kind: ServiceAccount - name: $OPERATOR_SERVICE_ACCOUNT - namespace: $NAMESPACE diff --git a/redis/schema.yaml b/schema.yaml similarity index 50% rename from redis/schema.yaml rename to schema.yaml index f1885ad..84bfc7e 100644 --- a/redis/schema.yaml +++ b/schema.yaml @@ -16,6 +16,14 @@ x-google-marketplace: type: REPO_WITH_REGISTRY operator.image.tag: type: TAG + billingagent: + properties: + billingagent.image: + type: FULL + usagemeter: + properties: + usagemeter.image: + type: FULL clusterConstraints: resources: - replicas: 3 @@ -37,6 +45,74 @@ properties: x-google-marketplace: type: SERVICE_ACCOUNT serviceAccount: + description: Service account + roles: + - type: ClusterRole + rulesType: CUSTOM + rules: + - apiGroups: ["apiextensions.k8s.io"] + resources: ["customresourcedefinitions"] + verbs: ["get"] + - type: Role + rulesType: CUSTOM + rules: + - apiGroups: ["rbac.authorization.k8s.io", ""] + resources: ["roles", "serviceaccounts", "rolebindings"] + verbs: ["*"] + - apiGroups: + - app.redislabs.com + resources: + - "*" + verbs: + - "*" + - apiGroups: [""] + resources: ["secrets"] + verbs: ["*"] + - apiGroups: [""] + resources: ["endpoints"] + verbs: ["get", "list", "watch"] + - apiGroups: [""] + resources: ["events"] + verbs: ["create"] + - apiGroups: ["apps"] + resources: ["deployments", "statefulsets", "replicasets"] + verbs: ["*"] + - apiGroups: ["policy"] + resources: ["poddisruptionbudgets"] + verbs: ["create", "delete", "get"] + - apiGroups: [""] + resources: ["configmaps"] + verbs: ["create", "delete", "get" , "update", "list", "watch"] + - apiGroups: [""] + resources: ["persistentvolumeclaims"] + verbs: ["create", "delete", "get" , "update"] + # needed rbac rules for services controller + - apiGroups: [""] + resources: ["pods"] + verbs: ["get", "watch", "list", "update", "patch"] + - apiGroups: [""] + resources: ["services"] + verbs: ["get", "watch", "list", "update", "patch", "create", "delete"] + - apiGroups: + - route.openshift.io + resources: ["routes", "routes/custom-host"] + verbs: ["*"] + - apiGroups: ["extensions"] + resources: ["podsecuritypolicies"] + resourceNames: + - redis-enterprise-psp + verbs: + - use + - apiGroups: ["extensions"] + resources: ["ingresses"] + verbs: ["*"] + CRJobServiceAccount: + type: string + title: Service account used by Redis Operator + x-google-marketplace: + type: SERVICE_ACCOUNT + serviceAccount: + description: Service account roles: - type: ClusterRole rulesType: PREDEFINED @@ -55,11 +131,6 @@ properties: # https://www.w3.org/TR/html52/sec-forms.html#email-state-typeemail pattern: ^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$ default: admin@example.com - operator.imageUbbAgent: - type: string - default: gcr.io/proven-reality-226706/redislabs/ubbagent:1.15 - x-google-marketplace: - type: IMAGE ingressAvailable: type: boolean default: true @@ -91,13 +162,14 @@ properties: x-google-marketplace: type: SERVICE_ACCOUNT serviceAccount: + description: Service account roles: - type: ClusterRole rulesType: CUSTOM rules: - apiGroups: ["apiextensions.k8s.io"] resources: ["customresourcedefinitions"] - verbs: ["get","list","create"] + verbs: ["get","list","create", "patch"] deployerHelm.image: type: string x-google-marketplace: diff --git a/usage-meter/Dockerfile b/usage-meter/Dockerfile new file mode 100644 index 0000000..87aa8a8 --- /dev/null +++ b/usage-meter/Dockerfile @@ -0,0 +1,12 @@ +FROM python:3.8-slim + +RUN apt-get update && apt-get install -y curl +RUN pip install kubernetes requests +RUN mkdir -p /app +COPY run.sh /app/ +COPY common.py /app/ +COPY meter.py /app/ +COPY tier_pricing.py /app/ +WORKDIR /app + +ENTRYPOINT ["/bin/sh", "/app/run.sh"] diff --git a/usage-meter/common.py b/usage-meter/common.py new file mode 100644 index 0000000..a191500 --- /dev/null +++ b/usage-meter/common.py @@ -0,0 +1,89 @@ +import os + +from kubernetes import client, config +from kubernetes.client.rest import ApiException + +from base64 import b64decode + +import yaml + +apis = { + 'rec' : { + 'group' : 'app.redislabs.com', + 'version' : 'v1', + 'kind' : 'RedisEnterpriseCluster', + 'plural' : 'redisenterpriseclusters' + }, + 'redb' : { + 'group' : 'app.redislabs.com', + 'version' : 'v1alpha1', + 'kind' : 'RedisEnterpriseDatabase', + 'plural' : 'redisenterprisedatabases' + } +} + +def get_api(name): + return apis.get(name).copy() + +def _current_namespace_from_kubeconfig(): + location = os.path.expanduser(os.environ.get('KUBECONFIG', '~/.kube/config')) + with open(location,'r') as config_data: + kconfig = yaml.load(config_data,Loader=yaml.Loader) + current_context = kconfig.get('current-context') + if current_context is not None: + for context in kconfig.get('contexts',[]): + if current_context==context.get('name'): + cluster = context.get('context',{}) + return cluster.get('namespace') + return None + +NAMESPACE_LOCATION='/var/run/secrets/kubernetes.io/serviceaccount/namespace' + +def bootstrap(use_config=False,namespace=None): + if use_config or os.getenv('KUBERNETES_SERVICE_HOST') is None: + config.load_kube_config() + return namespace if namespace is not None else _current_namespace_from_kubeconfig() + else: + config.load_incluster_config() + + if os.path.isfile(NAMESPACE_LOCATION): + with open(NAMESPACE_LOCATION,'r') as data: + ns = data.read().strip() + return ns + else: + return None + +def get_rec_specs(namespace): + + try: + + custom_objects = client.CustomObjectsApi() + api_spec = get_api('rec') + obj_list = custom_objects.get_namespaced_custom_object(api_spec['group'],api_spec['version'],namespace,api_spec['plural'],'') + + return [(obj_list['items'][0]['metadata']['name'],item['spec']) for item in obj_list['items']] + + except ApiException as e: + if e.status==404: + return [] + else: + print('{}: {}'.format(str(e.status),e.reason),file=sys.stderr) + return [] + +def get_rec_api(namespace,name): + dns_name = name + '.' + namespace + '.svc' + url = 'https://' + dns_name + ':9443' + + try: + + api = client.CoreV1Api() + secret = api.read_namespaced_secret(name,namespace) + password = secret.data.get('password') + username = secret.data.get('username') + return url, b64decode(username).decode() if username is not None else None, b64decode(password).decode() if password is not None else None, + + except ApiException as e: + if e.status==404: + return None + else: + raise e diff --git a/usage-meter/meter.py b/usage-meter/meter.py new file mode 100644 index 0000000..180ba34 --- /dev/null +++ b/usage-meter/meter.py @@ -0,0 +1,180 @@ +import sys +import argparse +import signal +import time +import datetime +import importlib + +from common import bootstrap, get_rec_specs, get_rec_api + +import requests +import urllib3 +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + +running = True +def sigterm_handler(signal,frame): + running = False + sys.print('SIGTERM received - terminating process.') +signal.signal(signal.SIGTERM, sigterm_handler) + +def tstamp(): + return datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc).isoformat() + +def parse_numberunit(value): + unit_pos = -1 + for index, digit in enumerate(value): + if not digit.isdigit(): + unit_pos = index + break + if unit_pos < 0: + return float(value), None + else: + return float(value[0:unit_pos]),value[unit_pos:] + + +def parse_cpu(value): + scalar, unit = parse_numberunit(value) + if unit == 'm': + scalar / 1000.0 + return scalar + +memory_units = { + 'K' : 10**3, + 'M' : 10**6, + 'G' : 10**9, + 'T' : 10**12, + 'P' : 10**15, + 'Ki' : 2**10, + 'Mi' : 2**20, + 'Gi' : 2**30, + 'Ti' : 2**40, + 'Pi' : 2**50 +} + +def parse_memory(value): + scalar, unit = parse_numberunit(value) + if unit is not None: + if unit not in memory_units: + return None + multiplier = memory_units[unit] + scalar = scalar * multiplier + scalar = scalar / memory_units['Gi'] + return scalar + +def requested_value(current,resources,category,item,parser): + if category in resources: + if item in resources[category]: + value = parser(resources[category][item]) + if value > current: + current = value + return current + +def get_shards(namespace, name): + base_url, username, password = get_rec_api(namespace,name) + + response = requests.get(base_url + '/v1/nodes',auth=(username,password),verify=False) + if response.status_code != 200: + raise Exception('Cannot get nodes from REST API {}, status={}'.format(base_url,response.status_code)) + nodes = response.json() + response = requests.get(base_url + '/v1/license',auth=(username,password),verify=False) + if response.status_code != 200: + raise Exception('Cannot get license from REST API {}, status={}'.format(base_url,response.status_code)) + license = response.json() + used = 0 + for node in nodes: + count = node.get('shard_count') + if count is not None: + used += count + max = license.get('shards_limit') + + return used, max + +def send_report(endpoint,data): + response = requests.post(endpoint,json=data) + if response.status_code < 200 or response.status_code >= 300: + print('Cannot post report, status={}'.format(response.status_code)) + return False + return True + +def print_report(name,start,end,data): + print('{}: {} {} {}'.format(name,start,end,data),flush=True) + return True +def report_usage(namespace,interval=60,shards=False,report=print_report): + start = tstamp() + since_last_report = 0 + while running: + time.sleep(interval) + end = tstamp() + since_last_report += interval + try: + sent = True + for name, rec in get_rec_specs(namespace): + cpu = 2.0 + memory = 4.0 + if 'redisEnterpriseNodeResources' in rec: + resources = rec['redisEnterpriseNodeResources'] + cpu = requested_value(cpu,resources,'requests','cpu',parse_cpu) + cpu = requested_value(cpu,resources,'limits','cpu',parse_cpu) + memory = requested_value(memory,resources,'requests','memory',parse_memory) + memory = requested_value(memory,resources,'limits','memory',parse_memory) + shards_used = 0 + shards_max = 0 + if shards: + shards_used, shards_max = get_shards(namespace,name) + success = report(name,start,end,{'interval':since_last_report,'cpu':cpu,'memory':memory,'shards_used':shards_used,'shards_max':shards_max}) + if not success: + sent = False + if sent: + since_last_report = 0 + start = tstamp() + except Exception as e: + print(e,file=sys.stderr) + + + +if __name__ == "__main__": + + argparser = argparse.ArgumentParser(description='kubectl-redis-rec') + argparser.add_argument('--verbose',help='Verbose output',action='store_true',default=False) + argparser.add_argument('--use-config',help='Use the .kubeconfig',action='store_true',default=False) + argparser.add_argument('--interval',help='Usage interval (in seconds)',type=int,default=60) + argparser.add_argument('--namespace',help='The namespace; defaults to the context.') + argparser.add_argument('--send-to',help='The URL of the endpoint to report the usage.') + argparser.add_argument('--shards',help='Enable shard usage report',action='store_true',default=False) + argparser.add_argument('--metric-name',help='The metric to report') + argparser.add_argument('--report-value',help='The value to report',choices=['interval','cpu','memory','shards_used','shards_max'],default='interval') + argparser.add_argument('--infer-metric-module',help='An function to compute the metric name') + argparser.add_argument('--infer-metric-name',help='An function to compute the metric name') + + args = argparser.parse_args() + + namespace = bootstrap(use_config=args.use_config,namespace=args.namespace) + + if namespace is None: + print('Cannot determine current namespace.',file=sys.stderr) + sys.exit(1) + + metric_namer = (lambda data : args.metric_name) if args.metric_name is not None else (lambda data : args.report_value) + if args.infer_metric_name is not None: + if args.infer_metric_module: + m = importlib.import_module(args.infer_metric_module) + metric_namer = getattr(m,args.infer_metric_name) + else: + metric_name = eval(args.infer_metric_name) + + report = print_report + + if args.send_to: + def sender(name,start,end,data): + metric = metric_namer(data) + value = int(data[args.report_value]) + usage_data = { + 'name' : metric, + 'startTime' : start, + 'endTime' : end, + 'value' : { 'int64Value' : value} + } + return send_report(args.send_to,usage_data) + report = sender + + report_usage(namespace,interval=args.interval,shards=args.shards,report=report); diff --git a/usage-meter/run.sh b/usage-meter/run.sh new file mode 100755 index 0000000..f5d633e --- /dev/null +++ b/usage-meter/run.sh @@ -0,0 +1,7 @@ +#!/bin/bash +MY_INTERVAL=${INTERVAL:-300} +MY_ENDPOINT=${ENDPOINT:-http://localhost:8888} +TIER_METRICS="--infer-metric-module tier_pricing --infer-metric-name tier_from_usage" +MY_METRICS=${METRICS:-$TIER_METRICS} +trap 'exit 255' TERM +python meter.py --interval $MY_INTERVAL --send-to $MY_ENDPOINT $MY_METRICS diff --git a/usage-meter/tier_pricing.py b/usage-meter/tier_pricing.py new file mode 100644 index 0000000..79ab100 --- /dev/null +++ b/usage-meter/tier_pricing.py @@ -0,0 +1,21 @@ +tiers = [ +'small_node_time', +'small_high_memory_node_time', +'medium_high_memory_node_time', +'large_high_memory_node_time', +'extra_large_high_memory_node_time' +] +cpu_tiers = [32,16,8,4,0] +memory_tiers = [208,104,52,26,0] + +def tier_from_usage(data): + tier = 0 + cpu = data.get('cpu') + for index, value in enumerate(cpu_tiers): + if cpu >= value: + tier = max(tier,len(tiers)-index-1) + memory = data.get('memory') + for index, value in enumerate(memory_tiers): + if memory >= value: + tier = max(tier,len(tiers)-index-1) + return tiers[tier] diff --git a/var.Makefile b/var.Makefile deleted file mode 100644 index 9149af6..0000000 --- a/var.Makefile +++ /dev/null @@ -1,65 +0,0 @@ -ifndef __VAR_MAKEFILE__ - -__VAR_MAKEFILE__ := included - -# Provides a class of targets that ensures variables are -# defined and trigger rebuilds when variable values change. -# -# Usage: -# -# my_target: .build/var/REGISTRY -# -# my_target rebuilds when the value of $(REGISTRY) changes. -# .build/var/REGISTRY also ensures that $(REGISTRY) value -# is non-empty. - - -# The main target that this Makefile offers. -# This is a real file that gets updated when a variable value -# change is detected. This rule does not have a recipe. It -# relies on the %-phony prerequisite to detect the change and -# update the file. -.build/var/%: .build/var/%-required .build/var/%-phony ; - - -.build/var: - mkdir -p .build/var - - -# Since we can't make pattern targets phony, we make them -# effectively phony by depending on this phony target. -.PHONY: var/phony -var/phony: ; - - -# An effectively phony target that always runs to compare the current -# value of the variable (say VARNAME) with the content of the -# corresponding .build/var/VARNAME file. If the contents differ, -# the recipe updates .build/var/VARNAME, which effectively trigger -# rebuilding of targets depending on .build/var.VARNAME. -.build/var/%-phony: var/phony | .build/var - @ \ - var_key="$*" ; \ - var_val="${$*}" ; \ - var_val_old=$$(cat ".build/var/$$var_key" 2> /dev/null) ; \ - if [ "$$var_val" != "$$var_val_old" ]; then \ - $(call print_notice,$$var_key has been updated) ; \ - printf " old value: $$var_val_old\n" ; \ - printf " new value: $$var_val\n" ; \ - printf "$$var_val" > .build/var/$$var_key ; \ - fi - - -# An effectively phony target that verifies that the variable -# has a non-empty value. -.build/var/%-required: var/phony - @ \ - var_key="$*" ; \ - var_val="${$*}" ; \ - if [ "$$var_val" = "" ]; then \ - $(call print_notice,Make variable '$*' is required); \ - exit 1; \ - fi - - -endif From ffd672cf1369e03498eb14f88f8cb21ec11c60fc Mon Sep 17 00:00:00 2001 From: Sambasiva Andaluri Date: Mon, 22 Mar 2021 21:17:05 -0400 Subject: [PATCH 055/120] fix readme --- README.md | 217 +++++------------------------------------------------- 1 file changed, 20 insertions(+), 197 deletions(-) diff --git a/README.md b/README.md index 71032d1..e67f8ee 100644 --- a/README.md +++ b/README.md @@ -22,14 +22,6 @@ The deployment creates two services: Get up and running with a few clicks! Install this Redis Enterprise app to a Google Kubernetes Engine cluster using Google Cloud Marketplace. You can do this from the Applications tab in the GKE page in the Cloud Console. -## Command line instructions -For testing, you may want to deploy straight from your command line, partially simulating what Marketplace does when it deploys. - -This is not a perfect simulation. For example, this entire process will not work unless you provide the [secrets](https://kubernetes.io/docs/concepts/configuration/secret/). - -Steps here are idempotent so feel free to just rerun steps in a script if you are working on later steps. - - ### Prerequisites #### Set up command-line tools @@ -40,10 +32,12 @@ You'll need the following tools in your development environment: - [kubectl](https://kubernetes.io/docs/reference/kubectl/overview/) - [docker](https://docs.docker.com/install/) - [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) +- [marketplace tools](https://github.com/GoogleCloudPlatform/marketplace-k8s-app-tools/blob/master/docs/tool-prerequisites.md) Configure `gcloud` as a Docker credential helper: ```shell +gcloud auth login gcloud auth configure-docker ``` @@ -63,17 +57,20 @@ Configure `kubectl` to connect to the new cluster. ```shell gcloud container clusters get-credentials "$CLUSTER" --zone "$ZONE" ``` +Create a namespace where redis cluster and database should be created -#### Clone this repo - -Required: Clone this repo. ```shell -git clone https://github.com/RedisLabs/gkemarketplace +kubectl create namespace redis ``` -Required: Clone ubbagent (monitors for billing) +#### Clone the repos + + ```shell -git clone https://github.com/RedisLabs/ubbagent.git +git clone https://github.com/GoogleCloudPlatform/click-to-deploy +cd k8s +git clone https://github.com/RedisLabs/gkemarketplace +``` ``` Optional: For reference, you can get RedisLabs Enterprise K8s Operator code (i.e., unrelated to Google MP) @@ -86,125 +83,25 @@ Optional: For reference, you can get MP K8s tools, examples, and instructions git clone https://github.com/GoogleCloudPlatform/marketplace-k8s-app-tools ``` -#### Install the Application resource definition - -An Application resource is an addition to the Kubernetes metamodel: A collection of individual Kubernetes components, such as Services, Deployments, etc, that you can manage as a group. - -The Application resource is defined by the [Kubernetes SIG-apps](https://github.com/kubernetes/community/tree/master/sig-apps) community. The source code can be found on -[github.com/kubernetes-sigs/application](https://github.com/kubernetes-sigs/application). - -To add Application to the metamodel and thus set up your cluster to understand Application resources, run the following command. - -```shell -kubectl apply -f "https://raw.githubusercontent.com/GoogleCloudPlatform/marketplace-k8s-app-tools/master/crd/app-crd.yaml" -``` - -### Install the Application - -Go to the `gkemarketplace` directory: +#### Building ```shell cd gkemarketplace +make clean +make app/build ``` -#### Configure the app with environment variables - -Choose an instance name and[namespace](https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/) -for the app as follows. In most cases, you can use the `default` namespace. - -```shell -export APP_INSTANCE_NAME=redis-labs-1 -export NAMESPACE=default -``` - -Set the number of replicas: - -```shell -export REPLICAS=3 -``` - -Set the username for the app: - -```shell -export REDIS_ADMIN=admin@acme.com -``` - -Set the CPU and Memory for nodes: - -```shell -export NODE_CPU=1000 -export NODE_MEM=1 -``` - - -Configure the container images. Update version numbers as necessary. - -```shell -export IMAGE_REDIS=gcr.io/proven-reality-226706/redislabs:$VERSION -export IMAGE_UBBAGENT=gcr.io/proven-reality-226706/redislabs/ubbagent:$VERSION -``` -Set the version - -**Note**: Do not use a patch number like 1.12.0; use only major-minor. +#### Deploying to GKE -```shell -export VERSION=. -``` - -#### Create namespace in your Kubernetes cluster - -Run the command below to create a new namespace. It is idempotent. - -```shell -kubectl create namespace "$NAMESPACE" -``` - -#### Prerequisite: Role-Based Access Control - -To use [role-based access control](https://kubernetes.io/docs/reference/access-authn-authz/rbac/) for the app, grant your user the ability to create roles in -Kubernetes: - -```shell -kubectl create clusterrolebinding cluster-admin-binding \ - --clusterrole cluster-admin \ - --user $(gcloud config get-value account) -``` - -For steps to enable role-based access control in Google Kubernetes Engine, see the [Kubernetes Engine documentation](https://cloud.google.com/kubernetes-engine/docs/how-to/role-based-access-control). - -#### Expand the manifest template - -We are using the `envsubst` approach to expand the template. (An alternative approach is with Helm and will be used soon.) -We recommend that you save the expanded manifest file for future updates to the application. - -1. Expand `RBAC` YAML file. You can configure the RBAC first. - - ```shell - # Define name of service account - export SERVICE_ACCOUNT=redis-enterprise-operator - - # Expand rbac.yaml.template - envsubst '$APP_INSTANCE_NAME $NAMESPACE $SERVICE_ACCOUNT' < manifest/rbac.yaml.template > "${APP_INSTANCE_NAME}_rbac.yaml" - ``` - -1. Expand `Application`/`crd`/`operator`/`ConfigMap` YAML files. - - ```shell - awk 'FNR==1 {print "---"}{print}' manifest/* \ - | envsubst '$APP_INSTANCE_NAME $NAMESPACE $IMAGE_REDIS $REPLICAS $REDIS_ADMIN $SERVICE_ACCOUNT $TAG $IMAGE_ - AGENT $NODE_CPU $NODE_MEM' \ - > "${APP_INSTANCE_NAME}_manifest.yaml" - ``` +An Application resource is an addition to the Kubernetes metamodel: A collection of individual Kubernetes components, such as Services, Deployments, etc, that you can manage as a group. -#### Apply the manifest to your Kubernetes cluster +The Application resource is defined by the [Kubernetes SIG-apps](https://github.com/kubernetes/community/tree/master/sig-apps) community. The source code can be found on +[github.com/kubernetes-sigs/application](https://github.com/kubernetes-sigs/application). -Use `kubectl` to apply the manifest to your Kubernetes cluster: ```shell -kubectl apply -f "${APP_INSTANCE_NAME}_rbac.yaml" --namespace "${NAMESPACE}" -# crd.yaml: Custom Resource Definition -kubectl apply -f deployer/crd.yaml -kubectl apply -f "${APP_INSTANCE_NAME}_manifest.yaml" --namespace "${NAMESPACE}" +make crd/install +mpdev install --deployer= --parameters='{"name": "redis-enterprise-operator", "namespace": "redis", "operator.nodeCpu": 5000, "operator.nodeMem": 16, "reportingSecret": "gs://cloud-marketplace-tools/reporting_secrets/fake_reporting_secret.yaml"} ``` #### View the app in the Google Cloud Platform Console @@ -253,77 +150,3 @@ kubectl get services -n $NAMESPACE 1. On the Application Details page, click **Delete**. -## Using the command line - -### Prepare the environment - -Set your installation name and Kubernetes namespace: - -```shell -export APP_INSTANCE_NAME=redis-enterprise-1 -export NAMESPACE=default -``` - -### Delete the resources - -**NOTE:** We recommend to use a kubectl version that is the same as the version of your cluster . - -To delete the resources, use the expanded manifest file used for the installation. - -Run `kubectl` on the expanded manifest file: - -```shell -kubectl delete -f "${APP_INSTANCE_NAME}_manifest.yaml" --namespace "${NAMESPACE}" -kubectl delete -f "${APP_INSTANCE_NAME}_rbac.yaml" --namespace "${NAMESPACE}" -``` - -Alternatively, delete the resources using types and a label: - -```shell -kubectl delete statefulset,secret,service,configmap,serviceaccount,role,rolebinding,application \ - --namespace $NAMESPACE \ - --selector app.kubernetes.io/name=$APP_INSTANCE_NAME -``` - -### Delete the persistent volumes of your installation - -By design, removal of StatefulSets in Kubernetes does not remove -PersistentVolumeClaims that were attached to their Pods. This prevents your -installations from accidentally deleting stateful data. - -To remove the PersistentVolumeClaims with their attached persistent disks: - -```shell -for pv in $(kubectl get pvc --namespace $NAMESPACE \ - --selector app.kubernetes.io/name=$APP_INSTANCE_NAME \ - --output jsonpath='{.items[*].spec.volumeName}'); -do - kubectl delete pv/$pv --namespace $NAMESPACE -done - -kubectl delete persistentvolumeclaims \ - --namespace $NAMESPACE \ - --selector app.kubernetes.io/name=$APP_INSTANCE_NAME -``` - -### Delete the GKE cluster - -``` -gcloud container clusters delete "$CLUSTER" --zone "$ZONE" -``` - -# Upgrading the version - -When a new version of the Redis Operator comes out, you will want to upgrade the version. - -# Upgrade `OP_VERSION=5.4.6-1186` in `Makefile`. -# Push -# Increment `Makefile:TAG ?= $VERSION` in `Makefile`. This will increment the version of both this Marketplace package and the UBB image that provides the sidecar. -# In repo for `ubbagent` (`https://github.com/RedisLabs/ubbagent.git`), set `TAG` env variable to new version, e.g. 1.15, then - `docker build -t gcr.io/proven-reality-226706/redislabs/ubbagent:$TAG .` - `docker push gcr.io/proven-reality-226706/redislabs/ubbagent:$TAG` - -4. Back in `gkemarketplace` `make -B app/build` (where `-B` forces the build even if no change is detected). - * This builds the image and pushes it to gcr.io - -(`cloudbuild.yaml` allows building this in Cloud Buld instead of `make`. It is not yet in active use, pending permissions for triggers and adoption of a process. ) From 71e91fe65fe4eea43580aa7ad71f6ec23e02b98b Mon Sep 17 00:00:00 2001 From: Sambasiva Andaluri Date: Tue, 23 Mar 2021 04:24:29 -0400 Subject: [PATCH 056/120] fix markdown in readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e67f8ee..9d05a5f 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,6 @@ Create a new cluster from the command line. The command is idempotent so runs af ```shell export CLUSTER=redis-cluster export ZONE=us-west1-a - gcloud container clusters create "$CLUSTER" --zone "$ZONE" ``` @@ -72,13 +71,14 @@ cd k8s git clone https://github.com/RedisLabs/gkemarketplace ``` -``` Optional: For reference, you can get RedisLabs Enterprise K8s Operator code (i.e., unrelated to Google MP) + ```shell git clone https://github.com/RedisLabs/redis-enterprise-k8s-docs.git ``` Optional: For reference, you can get MP K8s tools, examples, and instructions + ```shell git clone https://github.com/GoogleCloudPlatform/marketplace-k8s-app-tools ``` From 8960a4e5ca26aa7f9976e290285b8a799f6aa9d7 Mon Sep 17 00:00:00 2001 From: Sambasiva Andaluri Date: Mon, 29 Mar 2021 10:32:29 -0400 Subject: [PATCH 057/120] promote to staging --- Makefile | 2 +- README.md | 6 +++++- chart/redis-operator/values.yaml | 4 ++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index f8fa8c5..55fb17e 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ include ../gcloud.Makefile include ../var.Makefile #REGISTRY ?= marketplace.gcr.io/google/redis-enterprise-operator -REGISTRY := us-central1-docker.pkg.dev/sam-playground-290106/redis-mp +REGISTRY := us-central1-docker.pkg.dev/proven-reality-226706/redis-market-place $(info ---- REGISTRY = $(REGISTRY)) CHART_NAME := redis-operator diff --git a/README.md b/README.md index 9d05a5f..42faa1d 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,10 @@ gcloud auth login gcloud auth configure-docker ``` +#### Permissions + +User who builds and deploys the solution would need "Kubernetes Engine Admin" and "Editor" permissions. + #### Create a Google Kubernetes Engine cluster Create a new cluster from the command line. The command is idempotent so runs after the first are not needed, but do no harm. @@ -51,7 +55,7 @@ export ZONE=us-west1-a gcloud container clusters create "$CLUSTER" --zone "$ZONE" ``` -Configure `kubectl` to connect to the new cluster. +Configure `kubectl` to connect to the new cluster. The following is optional as by default cluster create adds credentials to local kube config. ```shell gcloud container clusters get-credentials "$CLUSTER" --zone "$ZONE" diff --git a/chart/redis-operator/values.yaml b/chart/redis-operator/values.yaml index 214aaad..52c3b78 100644 --- a/chart/redis-operator/values.yaml +++ b/chart/redis-operator/values.yaml @@ -6,6 +6,6 @@ operator: ubbagent: image: null billingagent: - image: us-central1-docker.pkg.dev/sam-playground-290106/redis-mp/billing-agent:6.0.12-5 + image: us-central1-docker.pkg.dev/proven-reality-226706/redis-market-place/billing-agent:6.0.12-5 usagemeter: - image: us-central1-docker.pkg.dev/sam-playground-290106/redis-mp/usage-meter:6.0.12-5 + image: us-central1-docker.pkg.dev/proven-reality-226706/redis-market-place/usage-meter:6.0.12-5 From 20b816161647c180339585d700e53baa52dee7b8 Mon Sep 17 00:00:00 2001 From: Sambasiva Andaluri Date: Tue, 30 Mar 2021 09:26:26 -0400 Subject: [PATCH 058/120] change registry to gcr.io --- Makefile | 7 ++++++- chart/redis-operator/values.yaml | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 55fb17e..6c6503a 100644 --- a/Makefile +++ b/Makefile @@ -8,8 +8,13 @@ include ../crd.Makefile include ../gcloud.Makefile include ../var.Makefile +# Production repo #REGISTRY ?= marketplace.gcr.io/google/redis-enterprise-operator -REGISTRY := us-central1-docker.pkg.dev/proven-reality-226706/redis-market-place +# Artifact repo +#REGISTRY := us-central1-docker.pkg.dev/proven-reality-226706/redis-market-place +# Container repo +REGISTRY := gcr.io/proven-reality-226706/redislabs + $(info ---- REGISTRY = $(REGISTRY)) CHART_NAME := redis-operator diff --git a/chart/redis-operator/values.yaml b/chart/redis-operator/values.yaml index 52c3b78..49bd535 100644 --- a/chart/redis-operator/values.yaml +++ b/chart/redis-operator/values.yaml @@ -6,6 +6,6 @@ operator: ubbagent: image: null billingagent: - image: us-central1-docker.pkg.dev/proven-reality-226706/redis-market-place/billing-agent:6.0.12-5 + image: gcr.io/proven-reality-226706/redislabs/billing-agent:6.0.12-5 usagemeter: - image: us-central1-docker.pkg.dev/proven-reality-226706/redis-market-place/usage-meter:6.0.12-5 + image: gcr.io/proven-reality-226706/redislabs/usage-meter:6.0.12-5 From 0ad7e01bb07ec567b30ae9a0c0aad92b32d603a4 Mon Sep 17 00:00:00 2001 From: Sambasiva Andaluri Date: Thu, 1 Apr 2021 05:17:39 -0400 Subject: [PATCH 059/120] fix app icon --- chart/redis-operator/templates/application.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/chart/redis-operator/templates/application.yaml b/chart/redis-operator/templates/application.yaml index f40e113..7ff6b29 100644 --- a/chart/redis-operator/templates/application.yaml +++ b/chart/redis-operator/templates/application.yaml @@ -3,8 +3,7 @@ kind: Application # Question: The CRD creation is supposed to pause before creat metadata: name: "{{ .Release.Name }}" annotations: - kubernetes-engine.cloud.google.com/icon: >- - data:image/png;base64, {{ .Files.Get "logo.png" | b64enc }} {{/* Get logo.png from the root of chart */}} + kubernetes-engine.cloud.google.com/icon: data:image/png;base64,{{ .Files.Get "logo.png" | b64enc | trim }} marketplace.cloud.google.com/deploy-info: '{"partner_id": "redislabs-public", "product_id": {{ .Chart.Name | quote }}, "partner_name": "Redis Labs"}' labels: app.kubernetes.io/name: "{{ .Release.Name }}" From e8b216e816df90d3b4ddd311b340581a33c765c2 Mon Sep 17 00:00:00 2001 From: Sambasiva Andaluri Date: Fri, 2 Apr 2021 11:10:42 -0400 Subject: [PATCH 060/120] retagging images to make them GCP partner portal friendly --- Makefile | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 6c6503a..6e30490 100644 --- a/Makefile +++ b/Makefile @@ -26,7 +26,15 @@ $(info ---- REDIS_TAG = $(REDIS_TAG)) OPERATOR_TAG ?= 6.0.12-5 $(info ---- OPERATOR_TAG = $(OPERATOR_TAG)) -APP_DEPLOYER_IMAGE := $(REGISTRY)/deployer:$(OPERATOR_TAG) +# Deployer tag is used for displaying versions in partner portal. +# This version only support major.minor so the Redis version major.minor.patch +# is converted into more readable form of major.2 digit zero padded minor + patch +# without the hyphen +DEPLOYER_TAG = 6.001205 +$(info ---- DEPLOYER_TAG = $(DEPLOYER_TAG)) + +# Tag the deployer image with modified version. +APP_DEPLOYER_IMAGE := $(REGISTRY)/deployer:$(DEPLOYER_TAG) NAME ?= redis-enterprise-operator-1 NAMESPACE ?= redis @@ -40,6 +48,7 @@ TESTER_IMAGE ?= $(REGISTRY)/tester:$(OPERATOR_TAG) app/build:: .build/redis-enterprise-operator/deployer \ .build/redis-enterprise-operator/redis \ + .build/redis-enterprise-operator/primary \ .build/redis-enterprise-operator/operator \ .build/redis-enterprise-operator/k8s-controller \ .build/redis-enterprise-operator/usage-meter \ @@ -108,6 +117,21 @@ app/build:: .build/redis-enterprise-operator/deployer \ docker push "$(REGISTRY)/redis:$(REDIS_TAG)" @touch "$@" +# Operator image is the what Google calls the primary image. +# Label the primary image with the same tag as deployer image. +# From the partner portal, primary image is queried using the same tag +# as deployer image. When pulling the image from docker hub use +# the redis native tag and push that image as primary image with deployer tag. +.build/redis-enterprise-operator/primary: .build/var/REGISTRY \ + .build/var/OPERATOR_TAG \ + .build/var/DEPLOYER_TAG \ + | .build/redis-enterprise-operator + $(call print_target, $@) + docker pull redislabs/operator:$(OPERATOR_TAG) + docker tag redislabs/operator:$(OPERATOR_TAG) "$(REGISTRY):$(DEPLOYER_TAG)" + docker push "$(REGISTRY):$(DEPLOYER_TAG)" + @touch "$@" + .build/redis-enterprise-operator/operator: .build/var/REGISTRY \ .build/var/OPERATOR_TAG \ | .build/redis-enterprise-operator From fe81e5bcd062c8a20a5f34ae24ccd97d867b4730 Mon Sep 17 00:00:00 2001 From: Alex Milowski Date: Mon, 5 Apr 2021 18:47:20 -0700 Subject: [PATCH 061/120] adjustments to allow publish minimal containers; usage meter version; adjusted image tagging --- Makefile | 20 +++++++++---------- .../templates/deployment/operator.yaml | 7 ++----- chart/redis-operator/values.yaml | 8 ++++---- deployer/Dockerfile | 14 ++++++------- schema.yaml | 10 ++++------ 5 files changed, 26 insertions(+), 33 deletions(-) diff --git a/Makefile b/Makefile index 6e30490..c9be5db 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,7 @@ $(info ---- OPERATOR_TAG = $(OPERATOR_TAG)) # This version only support major.minor so the Redis version major.minor.patch # is converted into more readable form of major.2 digit zero padded minor + patch # without the hyphen -DEPLOYER_TAG = 6.001205 +DEPLOYER_TAG ?= 6.001205 $(info ---- DEPLOYER_TAG = $(DEPLOYER_TAG)) # Tag the deployer image with modified version. @@ -44,16 +44,16 @@ APP_PARAMETERS ?= { \ "NAMESPACE": "$(NAMESPACE)" \ } -TESTER_IMAGE ?= $(REGISTRY)/tester:$(OPERATOR_TAG) +#TESTER_IMAGE ?= $(REGISTRY)/tester:$(OPERATOR_TAG) app/build:: .build/redis-enterprise-operator/deployer \ - .build/redis-enterprise-operator/redis \ .build/redis-enterprise-operator/primary \ - .build/redis-enterprise-operator/operator \ - .build/redis-enterprise-operator/k8s-controller \ .build/redis-enterprise-operator/usage-meter \ - .build/redis-enterprise-operator/billing-agent \ - .build/redis-enterprise-operator/tester + .build/redis-enterprise-operator/operator \ + # .build/redis-enterprise-operator/redis \ + # .build/redis-enterprise-operator/k8s-controller \ + # .build/redis-enterprise-operator/billing-agent \ + # .build/redis-enterprise-operator/tester .build/redis-enterprise-operator: | .build @@ -128,8 +128,8 @@ app/build:: .build/redis-enterprise-operator/deployer \ | .build/redis-enterprise-operator $(call print_target, $@) docker pull redislabs/operator:$(OPERATOR_TAG) - docker tag redislabs/operator:$(OPERATOR_TAG) "$(REGISTRY):$(DEPLOYER_TAG)" - docker push "$(REGISTRY):$(DEPLOYER_TAG)" + docker tag redislabs/operator:$(OPERATOR_TAG) "$(REGISTRY):$(OPERATOR_TAG)" + docker push "$(REGISTRY):$(OPERATOR_TAG)" @touch "$@" .build/redis-enterprise-operator/operator: .build/var/REGISTRY \ @@ -149,5 +149,3 @@ app/build:: .build/redis-enterprise-operator/deployer \ docker tag redislabs/k8s-controller:$(OPERATOR_TAG) "$(REGISTRY)/k8s-controller:$(OPERATOR_TAG)" docker push "$(REGISTRY)/k8s-controller:$(OPERATOR_TAG)" @touch "$@" - - diff --git a/chart/redis-operator/templates/deployment/operator.yaml b/chart/redis-operator/templates/deployment/operator.yaml index 1f3c807..29a7e08 100644 --- a/chart/redis-operator/templates/deployment/operator.yaml +++ b/chart/redis-operator/templates/deployment/operator.yaml @@ -43,7 +43,7 @@ spec: cpu: 500m memory: 256Mi - name: usage-meter - image: {{ .Values.usagemeter.image }} + image: {{ .Values.usagemeter.image }}:{{ .Values.usagemeter.tag }} imagePullPolicy: Always env: - name: INTERVAL @@ -52,9 +52,6 @@ spec: value: "http://localhost:3456" - name: METRICS value: "--shards --report-value shards_used" - - name: billing-agent - image: {{ .Values.billingagent.image }} - imagePullPolicy: Always - name: ubbagent image: gcr.io/cloud-marketplace-tools/metering/ubbagent imagePullPolicy: IfNotPresent @@ -87,4 +84,4 @@ spec: volumes: - name: ubb-configmap configMap: - name: {{ template "redis_operator.UBBAgentConfigMap" . }} \ No newline at end of file + name: {{ template "redis_operator.UBBAgentConfigMap" . }} diff --git a/chart/redis-operator/values.yaml b/chart/redis-operator/values.yaml index 49bd535..9fc34f6 100644 --- a/chart/redis-operator/values.yaml +++ b/chart/redis-operator/values.yaml @@ -2,10 +2,10 @@ operator: serviceAccountName: null image: repository: null - tag: null + tag: null ubbagent: image: null -billingagent: - image: gcr.io/proven-reality-226706/redislabs/billing-agent:6.0.12-5 usagemeter: - image: gcr.io/proven-reality-226706/redislabs/usage-meter:6.0.12-5 + image: + repository: null + tag: null diff --git a/deployer/Dockerfile b/deployer/Dockerfile index 29571c6..a6ea806 100644 --- a/deployer/Dockerfile +++ b/deployer/Dockerfile @@ -25,17 +25,17 @@ RUN cat /tmp/schema.yaml \ > /tmp/schema.yaml.new \ && mv /tmp/schema.yaml.new /tmp/schema.yaml -ADD apptest/deployer/schema.yaml /tmp/apptest/schema.yaml -RUN cat /tmp/apptest/schema.yaml \ - | env -i "REGISTRY=$REGISTRY" "TAG=$TAG" envsubst \ - > /tmp/apptest/schema.yaml.new \ - && mv /tmp/apptest/schema.yaml.new /tmp/apptest/schema.yaml +#ADD apptest/deployer/schema.yaml /tmp/apptest/schema.yaml +#RUN cat /tmp/apptest/schema.yaml \ +# | env -i "REGISTRY=$REGISTRY" "TAG=$TAG" envsubst \ +# > /tmp/apptest/schema.yaml.new \ +# && mv /tmp/apptest/schema.yaml.new /tmp/apptest/schema.yaml FROM gcr.io/cloud-marketplace-tools/k8s/deployer_helm:$MARKETPLACE_TOOLS_TAG ARG CHART_NAME COPY --from=build /tmp/$CHART_NAME.tar.gz /data/chart/ -COPY --from=build /tmp/test/$CHART_NAME.tar.gz /data-test/chart/ -COPY --from=build /tmp/apptest/schema.yaml /data-test/ +#COPY --from=build /tmp/test/$CHART_NAME.tar.gz /data-test/chart/ +#COPY --from=build /tmp/apptest/schema.yaml /data-test/ COPY --from=build /tmp/schema.yaml /data/ diff --git a/schema.yaml b/schema.yaml index 84bfc7e..a1f8b6e 100644 --- a/schema.yaml +++ b/schema.yaml @@ -16,14 +16,12 @@ x-google-marketplace: type: REPO_WITH_REGISTRY operator.image.tag: type: TAG - billingagent: - properties: - billingagent.image: - type: FULL usagemeter: properties: usagemeter.image: - type: FULL + type: REPO_WITH_REGISTRY + usagemeter.tag: + type: TAG clusterConstraints: resources: - replicas: 3 @@ -180,4 +178,4 @@ required: - operator.redisAdmin - operator.replicas - operator.nodeCpu -- operator.nodeMem \ No newline at end of file +- operator.nodeMem From 2963cd3193c885c9b24db6321a870bc312e2c336 Mon Sep 17 00:00:00 2001 From: Alex Milowski Date: Mon, 5 Apr 2021 19:47:43 -0700 Subject: [PATCH 062/120] fix for preserving existing CR --- chart/redis-operator/templates/job/cr-create.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/chart/redis-operator/templates/job/cr-create.yaml b/chart/redis-operator/templates/job/cr-create.yaml index 6495a39..393a60c 100644 --- a/chart/redis-operator/templates/job/cr-create.yaml +++ b/chart/redis-operator/templates/job/cr-create.yaml @@ -20,9 +20,10 @@ spec: - "/bin/bash" - "-ec" - | - for cr in /cr_to_create/*; - do cat $cr; kubectl apply -f $cr; - done + if ! kubectl get rec/redis-enterprise 2>/dev/null + then + kubectl apply -f /cr_to_create/redis-enterprise-cluster.yaml + fi image: {{ .Values.deployerHelm.image }} imagePullPolicy: Always name: cr-create From 0b8d150e67d985f6ec2a963af26fb1a2897f540e Mon Sep 17 00:00:00 2001 From: Alex Milowski Date: Tue, 6 Apr 2021 18:45:40 -0700 Subject: [PATCH 063/120] added docs for usage meter --- usage-meter/README.md | 59 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 usage-meter/README.md diff --git a/usage-meter/README.md b/usage-meter/README.md new file mode 100644 index 0000000..fb6c1ba --- /dev/null +++ b/usage-meter/README.md @@ -0,0 +1,59 @@ +# Usage Meter Sidecar + +## Overview + +For usage metering with Redis Enterprise, the `meter.py` program provides the +ability to read the cluster node sizing, shard usage, and shard maximum and +report that usage to a billing endpoint. + +## Collecting Usage + +The metrics that can be collected: + + * `interval` - the time period for the usage + * `memory` - the memory allocated to the RS node + * `cpu` - the cpu allocated to the RS node + * `shards_used` - the number of shards in use by the cluster + * `shards_max` - the maximum shards allowed by the licensed used by the cluster + +A report contains is send to the billing endpoint in JSON format that +contains: + + * `name` - the metric name + * `startTime` - an ISO 8601 timestamp of the start datetime of the period + * `endTime` - an ISO 8601 timestamp of the end datetime of the period + * `value` - the metric value (e.g., '{ "int64Value" : 10}') + +The `meter.py` program has the following options: + + * `--use-config` - enable using the .kubeconfig file for K8s credentials + * `--interval nn` - the collection interval (in seconds) + * `--namespace ns` - the namespace to inspect (defaults to the context or pod namespace) + * `--send-to url` - the URL of the reporting endpoint to which to send the reports + * `--metric-name name` - the name of the metric to report + * `--report-value name` - the value to report - must be one of metric names (e.g., shards_used). This defaults to the time period between the last succesful report and the current report. + * `--shards` - enable reading the shard used/max from the cluster REST API + * `--infer-metric-name` - a python function expression to infer the metric name. If the `--infer-metric-module` option is used, this value is the name of a function in that module. + * `--infer-metric-module` - the python module for the the expression. + +A metric name can be dynamically computed from the collected metrics. For example: + +``` +python meter.py --infer-metric-module tier_pricing --infer-metric-name tier_from_usage +``` + +## Sidecar usage + +See the example in [operator-with-meter.yaml](operator-with-meter.yaml). + +The meter container can be built via: + +``` +docker build -t you/redis-k8s-meter-usage:latest . +``` + +And the demonstration bill agent receiver via: + +``` +docker build -t you/flask-receiver:latest -f receiver.Dockerfile . +``` From e05b822a5df900930266cf3e78cdf10691ecf48c Mon Sep 17 00:00:00 2001 From: Alex Milowski Date: Tue, 6 Apr 2021 18:46:04 -0700 Subject: [PATCH 064/120] added metric name; fixed port --- .../templates/deployment/operator.yaml | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/chart/redis-operator/templates/deployment/operator.yaml b/chart/redis-operator/templates/deployment/operator.yaml index 29a7e08..766cab0 100644 --- a/chart/redis-operator/templates/deployment/operator.yaml +++ b/chart/redis-operator/templates/deployment/operator.yaml @@ -2,7 +2,7 @@ apiVersion: apps/v1 kind: Deployment metadata: name: redis-enterprise-operator - namespace: {{ .Release.Namespace }} + namespace: {{ .Release.Namespace }} spec: replicas: 1 selector: @@ -34,7 +34,7 @@ spec: - name: OPERATOR_NAME value: redis-enterprise-operator - name: DATABASE_CONTROLLER_ENABLED - value: "false" + value: "false" resources: limits: cpu: 4000m @@ -49,17 +49,13 @@ spec: - name: INTERVAL value: "60" - name: ENDPOINT - value: "http://localhost:3456" + value: "http://localhost:6080" - name: METRICS - value: "--shards --report-value shards_used" + value: "--shards --report-value shards_used --metric-name redislabs_db_shards_used_by_hour" - name: ubbagent image: gcr.io/cloud-marketplace-tools/metering/ubbagent imagePullPolicy: IfNotPresent env: - - name: NODE_CPU - value: "{{ .Values.operator.nodeCpu }}m" - - name: NODE_MEM - value: "{{ .Values.operator.nodeMem }}Gi" - name: AGENT_CONFIG_FILE value: /ubbagent/config.yaml - name: AGENT_LOCAL_PORT From 63b319182f86ebee8828e096c82f1081b8a42770 Mon Sep 17 00:00:00 2001 From: Alex Milowski Date: Wed, 7 Apr 2021 16:14:31 -0700 Subject: [PATCH 065/120] added logging; flushed errors --- usage-meter/meter.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/usage-meter/meter.py b/usage-meter/meter.py index 180ba34..b2383f8 100644 --- a/usage-meter/meter.py +++ b/usage-meter/meter.py @@ -90,18 +90,20 @@ def get_shards(namespace, name): return used, max def send_report(endpoint,data): + print('\x1E',data,sep='',flush=True) response = requests.post(endpoint,json=data) if response.status_code < 200 or response.status_code >= 300: - print('Cannot post report, status={}'.format(response.status_code)) + print('Cannot post report, status={}'.format(response.status_code),flush=True) return False return True -def print_report(name,start,end,data): - print('{}: {} {} {}'.format(name,start,end,data),flush=True) - return True -def report_usage(namespace,interval=60,shards=False,report=print_report): +def report_usage(namespace,interval=60,shards=False,report=None): start = tstamp() since_last_report = 0 + if report is None: + def printer(*args): + print(args,flush=True) + report = printer while running: time.sleep(interval) end = tstamp() @@ -162,6 +164,10 @@ def report_usage(namespace,interval=60,shards=False,report=print_report): else: metric_name = eval(args.infer_metric_name) + def print_report(name,start,end,data): + metric = metric_namer(data) + print('{}: {} {} {} {}'.format(name,metric,start,end,data),flush=True) + return True report = print_report if args.send_to: From be7ade7e3eba1df5823dac2f98180f6c0ee1ac4c Mon Sep 17 00:00:00 2001 From: Alex Milowski Date: Wed, 7 Apr 2021 16:15:17 -0700 Subject: [PATCH 066/120] adjustments to report metric directly; shortened aggregation time --- .../templates/configmap/ubbagent.yaml | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/chart/redis-operator/templates/configmap/ubbagent.yaml b/chart/redis-operator/templates/configmap/ubbagent.yaml index d1b072d..56b5c92 100644 --- a/chart/redis-operator/templates/configmap/ubbagent.yaml +++ b/chart/redis-operator/templates/configmap/ubbagent.yaml @@ -14,24 +14,15 @@ data: # Google Service Control. encodedServiceAccountKey: $AGENT_ENCODED_KEY metrics: - - name: shards_used + - name: small_node_time type: int endpoints: - name: servicecontrol aggregation: - bufferSeconds: 3600 + bufferSeconds: 180 endpoints: - name: servicecontrol servicecontrol: identity: gcp serviceName: redislabs.mp-redislabs-public.appspot.com consumerId: $AGENT_CONSUMER_ID - sources: - - name: redislabs.mp-redislabs-public.appspot.com - heartbeat: - metric: shards_used - intervalSeconds: 600 - value: - int64Value: 600 - labels: - auto: true \ No newline at end of file From 133239984e0e56fec740578d924e29d1338dbd69 Mon Sep 17 00:00:00 2001 From: Alex Milowski Date: Wed, 7 Apr 2021 16:15:58 -0700 Subject: [PATCH 067/120] enabled database controller; fixed ubbagent endpoint; changed to interval for testing via existing metric --- chart/redis-operator/templates/deployment/operator.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/chart/redis-operator/templates/deployment/operator.yaml b/chart/redis-operator/templates/deployment/operator.yaml index 766cab0..9e13c2e 100644 --- a/chart/redis-operator/templates/deployment/operator.yaml +++ b/chart/redis-operator/templates/deployment/operator.yaml @@ -34,7 +34,7 @@ spec: - name: OPERATOR_NAME value: redis-enterprise-operator - name: DATABASE_CONTROLLER_ENABLED - value: "false" + value: "true" resources: limits: cpu: 4000m @@ -49,9 +49,10 @@ spec: - name: INTERVAL value: "60" - name: ENDPOINT - value: "http://localhost:6080" + value: "http://localhost:6080/report" - name: METRICS - value: "--shards --report-value shards_used --metric-name redislabs_db_shards_used_by_hour" + # Using should be: -shards --report-value shards_used --metric-name redislabs_db_shards_used_by_hour + value: "--shards --report-value interval --metric-name small_node_time" - name: ubbagent image: gcr.io/cloud-marketplace-tools/metering/ubbagent imagePullPolicy: IfNotPresent From 899bfbb5dcf356b8d16ca1d1cfa1954d02744310 Mon Sep 17 00:00:00 2001 From: Sambasiva Andaluri Date: Thu, 8 Apr 2021 09:47:45 -0400 Subject: [PATCH 068/120] expose storage class as an optional param --- chart/redis-operator/templates/configmap/cr.yaml | 2 +- schema.yaml | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/chart/redis-operator/templates/configmap/cr.yaml b/chart/redis-operator/templates/configmap/cr.yaml index 98bf8e5..06bb848 100644 --- a/chart/redis-operator/templates/configmap/cr.yaml +++ b/chart/redis-operator/templates/configmap/cr.yaml @@ -18,7 +18,7 @@ data: nodes: {{ .Values.operator.replicas }} persistentSpec: enabled: true - storageClassName: "standard" + storageClassName: {{ .Values.operator.storageClass }} uiServiceType: "{{ if .Values.ingressAvailable -}}LoadBalancer{{- else -}}ClusterIP{{- end }}" username: "{{ .Values.operator.redisAdmin }}" redisEnterpriseNodeResources: diff --git a/schema.yaml b/schema.yaml index a1f8b6e..385fdab 100644 --- a/schema.yaml +++ b/schema.yaml @@ -140,6 +140,11 @@ properties: type: string x-google-marketplace: type: REPORTING_SECRET + operator.storageClass: + title: Storage Class + type: string + description: Storage class + default: standard operator.nodeCpu: title: Node CPU [millis] type: integer @@ -175,6 +180,7 @@ properties: required: - name - namespace +- operator.storageClass - operator.redisAdmin - operator.replicas - operator.nodeCpu From f1e0b2eb5d514c26a0d586bd5ebb6c55e3bd22b6 Mon Sep 17 00:00:00 2001 From: Alex Milowski Date: Thu, 8 Apr 2021 08:46:38 -0700 Subject: [PATCH 069/120] added time period to allow scaled reporting (e.g., fractional shard hour) --- usage-meter/meter.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/usage-meter/meter.py b/usage-meter/meter.py index b2383f8..b8d2fdb 100644 --- a/usage-meter/meter.py +++ b/usage-meter/meter.py @@ -140,6 +140,7 @@ def printer(*args): argparser.add_argument('--verbose',help='Verbose output',action='store_true',default=False) argparser.add_argument('--use-config',help='Use the .kubeconfig',action='store_true',default=False) argparser.add_argument('--interval',help='Usage interval (in seconds)',type=int,default=60) + argparser.add_argument('--time-period',help='The time period for reporting (e.g. per hour 3600)',type=int) argparser.add_argument('--namespace',help='The namespace; defaults to the context.') argparser.add_argument('--send-to',help='The URL of the endpoint to report the usage.') argparser.add_argument('--shards',help='Enable shard usage report',action='store_true',default=False) @@ -170,6 +171,11 @@ def print_report(name,start,end,data): return True report = print_report + scale = None + + if args.time_period: + scale = args.interval / args.time_period + if args.send_to: def sender(name,start,end,data): metric = metric_namer(data) @@ -178,7 +184,7 @@ def sender(name,start,end,data): 'name' : metric, 'startTime' : start, 'endTime' : end, - 'value' : { 'int64Value' : value} + 'value' : { 'int64Value' : value} if scale is None else {'doubleValue' : value * scale} } return send_report(args.send_to,usage_data) report = sender From b5db9f465cb1c760a33c7c8d9ebf0d4c37ad0e81 Mon Sep 17 00:00:00 2001 From: Alex Milowski Date: Thu, 8 Apr 2021 08:47:09 -0700 Subject: [PATCH 070/120] changed to shard hour fration pricing metric; changed reporting time period to 6 minutes --- chart/redis-operator/templates/configmap/ubbagent.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/chart/redis-operator/templates/configmap/ubbagent.yaml b/chart/redis-operator/templates/configmap/ubbagent.yaml index 56b5c92..c53c9a8 100644 --- a/chart/redis-operator/templates/configmap/ubbagent.yaml +++ b/chart/redis-operator/templates/configmap/ubbagent.yaml @@ -14,12 +14,12 @@ data: # Google Service Control. encodedServiceAccountKey: $AGENT_ENCODED_KEY metrics: - - name: small_node_time - type: int + - name: redislabs_db_shard_hour + type: double endpoints: - name: servicecontrol aggregation: - bufferSeconds: 180 + bufferSeconds: 360 endpoints: - name: servicecontrol servicecontrol: From 16b21bcb7c047d219859d52d67706d2dfb294ecc Mon Sep 17 00:00:00 2001 From: Alex Milowski Date: Thu, 8 Apr 2021 08:47:26 -0700 Subject: [PATCH 071/120] changed to use shard hour metric --- chart/redis-operator/templates/deployment/operator.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/chart/redis-operator/templates/deployment/operator.yaml b/chart/redis-operator/templates/deployment/operator.yaml index 9e13c2e..3e5597a 100644 --- a/chart/redis-operator/templates/deployment/operator.yaml +++ b/chart/redis-operator/templates/deployment/operator.yaml @@ -47,12 +47,11 @@ spec: imagePullPolicy: Always env: - name: INTERVAL - value: "60" + value: "360" - name: ENDPOINT value: "http://localhost:6080/report" - name: METRICS - # Using should be: -shards --report-value shards_used --metric-name redislabs_db_shards_used_by_hour - value: "--shards --report-value interval --metric-name small_node_time" + value: "--time-period 3600 --shards --report-value shards_used --metric-name redislabs_db_shard_hour" - name: ubbagent image: gcr.io/cloud-marketplace-tools/metering/ubbagent imagePullPolicy: IfNotPresent From 076ae34869677ff0a675fa21fad2c13d512fea54 Mon Sep 17 00:00:00 2001 From: Alex Milowski Date: Fri, 9 Apr 2021 11:21:27 -0700 Subject: [PATCH 072/120] Fix to use the primary image for the operator; removed separate push of operator --- Makefile | 6 +++--- chart/redis-operator/templates/deployment/operator.yaml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index c9be5db..d1ead09 100644 --- a/Makefile +++ b/Makefile @@ -49,7 +49,7 @@ APP_PARAMETERS ?= { \ app/build:: .build/redis-enterprise-operator/deployer \ .build/redis-enterprise-operator/primary \ .build/redis-enterprise-operator/usage-meter \ - .build/redis-enterprise-operator/operator \ + # .build/redis-enterprise-operator/operator \ # .build/redis-enterprise-operator/redis \ # .build/redis-enterprise-operator/k8s-controller \ # .build/redis-enterprise-operator/billing-agent \ @@ -119,8 +119,8 @@ app/build:: .build/redis-enterprise-operator/deployer \ # Operator image is the what Google calls the primary image. # Label the primary image with the same tag as deployer image. -# From the partner portal, primary image is queried using the same tag -# as deployer image. When pulling the image from docker hub use +# From the partner portal, primary image is queried using the same tag +# as deployer image. When pulling the image from docker hub use # the redis native tag and push that image as primary image with deployer tag. .build/redis-enterprise-operator/primary: .build/var/REGISTRY \ .build/var/OPERATOR_TAG \ diff --git a/chart/redis-operator/templates/deployment/operator.yaml b/chart/redis-operator/templates/deployment/operator.yaml index 3e5597a..9644db1 100644 --- a/chart/redis-operator/templates/deployment/operator.yaml +++ b/chart/redis-operator/templates/deployment/operator.yaml @@ -18,7 +18,7 @@ spec: {{- include "initContainerWaitForCRDsDeploy" . | nindent 6 }} containers: - name: redis-enterprise-operator - image: {{ .Values.operator.image.repository }}/operator:{{ .Values.operator.image.tag }} + image: {{ .Values.operator.image.repository }}:{{ .Values.operator.image.tag }} command: - redis-enterprise-operator imagePullPolicy: Always From 45a8cff52db76ad7c993fd54923493fa834aae7f Mon Sep 17 00:00:00 2001 From: Alex Milowski Date: Tue, 13 Apr 2021 08:57:53 -0700 Subject: [PATCH 073/120] ugrade scripts --- cleanup.sh | 4 ++++ upgrade.sh | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100755 cleanup.sh create mode 100755 upgrade.sh diff --git a/cleanup.sh b/cleanup.sh new file mode 100755 index 0000000..1b588d3 --- /dev/null +++ b/cleanup.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +kubectl delete job.batch/redis-enterprise-operator-cr-job +kubectl delete job.batch/redis-enterprise-operator-crd-job diff --git a/upgrade.sh b/upgrade.sh new file mode 100755 index 0000000..37143cb --- /dev/null +++ b/upgrade.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +RELEASE=${1:-6.001205} +REPO=${2:-gcr.io/proven-reality-226706/redislabs} +SA=$3 + +RELEASE_SUFFIX=$(echo $RELEASE | sed s/\\./-/g) +if [ -z "$SA" ]; +then +SA=redis-enterprise-operator-upgrade-${RELEASE_SUFFIX} +cat < Date: Wed, 14 Apr 2021 05:28:40 -0400 Subject: [PATCH 074/120] fix service account. cleanup makefile --- Makefile | 69 ++++--------------- apptest/tester/tests/basic-suite.yaml | 16 +---- .../templates/job/crd-create.yaml | 2 +- deployer/Dockerfile | 15 ++-- schema.yaml | 9 ++- 5 files changed, 31 insertions(+), 80 deletions(-) diff --git a/Makefile b/Makefile index d1ead09..e56295d 100644 --- a/Makefile +++ b/Makefile @@ -44,16 +44,12 @@ APP_PARAMETERS ?= { \ "NAMESPACE": "$(NAMESPACE)" \ } -#TESTER_IMAGE ?= $(REGISTRY)/tester:$(OPERATOR_TAG) +TESTER_IMAGE ?= $(REGISTRY)/tester:$(OPERATOR_TAG) app/build:: .build/redis-enterprise-operator/deployer \ .build/redis-enterprise-operator/primary \ .build/redis-enterprise-operator/usage-meter \ - # .build/redis-enterprise-operator/operator \ - # .build/redis-enterprise-operator/redis \ - # .build/redis-enterprise-operator/k8s-controller \ - # .build/redis-enterprise-operator/billing-agent \ - # .build/redis-enterprise-operator/tester + .build/redis-enterprise-operator/tester .build/redis-enterprise-operator: | .build @@ -80,44 +76,7 @@ app/build:: .build/redis-enterprise-operator/deployer \ docker push "$(APP_DEPLOYER_IMAGE)" @touch "$@" -.build/redis-enterprise-operator/tester: apptest/**/* \ - | .build/redis-enterprise-operator - $(call print_target, $@) - cd apptest/tester \ - && docker build --tag "$(TESTER_IMAGE)" . - docker push "$(TESTER_IMAGE)" - @touch "$@" - -.build/redis-enterprise-operator/usage-meter: usage-meter/**/* \ - .build/var/REGISTRY \ - .build/var/OPERATOR_TAG \ - | .build/redis-enterprise-operator - $(call print_target, $@) - cd usage-meter \ - && docker build --tag "$(REGISTRY)/usagemeter:$(OPERATOR_TAG)" . - docker push "$(REGISTRY)/usagemeter:$(OPERATOR_TAG)" - @touch "$@" - -.build/redis-enterprise-operator/billing-agent: billing-agent/**/* \ - .build/var/REGISTRY \ - .build/var/OPERATOR_TAG \ - | .build/redis-enterprise-operator - $(call print_target, $@) - cd billing-agent \ - && docker build --tag "$(REGISTRY)/billingagent:$(OPERATOR_TAG)" . - docker push "$(REGISTRY)/billingagent:$(OPERATOR_TAG)" - @touch "$@" - -.build/redis-enterprise-operator/redis: .build/var/REGISTRY \ - .build/var/REDIS_TAG \ - | .build/redis-enterprise-operator - $(call print_target, $@) - docker pull redislabs/redis:$(REDIS_TAG) - docker tag redislabs/redis:$(REDIS_TAG) "$(REGISTRY)/redis:$(REDIS_TAG)" - docker push "$(REGISTRY)/redis:$(REDIS_TAG)" - @touch "$@" - -# Operator image is the what Google calls the primary image. +# Operator image is the primary image for Redis Enterprise. # Label the primary image with the same tag as deployer image. # From the partner portal, primary image is queried using the same tag # as deployer image. When pulling the image from docker hub use @@ -132,20 +91,20 @@ app/build:: .build/redis-enterprise-operator/deployer \ docker push "$(REGISTRY):$(OPERATOR_TAG)" @touch "$@" -.build/redis-enterprise-operator/operator: .build/var/REGISTRY \ +.build/redis-enterprise-operator/usage-meter: usage-meter/**/* \ + .build/var/REGISTRY \ .build/var/OPERATOR_TAG \ - | .build/redis-enterprise-operator + | .build/redis-enterprise-operator $(call print_target, $@) - docker pull redislabs/operator:$(OPERATOR_TAG) - docker tag redislabs/operator:$(OPERATOR_TAG) "$(REGISTRY)/operator:$(OPERATOR_TAG)" - docker push "$(REGISTRY)/operator:$(OPERATOR_TAG)" + cd usage-meter \ + && docker build --tag "$(REGISTRY)/usagemeter:$(OPERATOR_TAG)" . + docker push "$(REGISTRY)/usagemeter:$(OPERATOR_TAG)" @touch "$@" -.build/redis-enterprise-operator/k8s-controller: .build/var/REGISTRY \ - .build/var/OPERATOR_TAG \ - | .build/redis-enterprise-operator +.build/redis-enterprise-operator/tester: apptest/**/* \ + | .build/redis-enterprise-operator $(call print_target, $@) - docker pull redislabs/k8s-controller:$(OPERATOR_TAG) - docker tag redislabs/k8s-controller:$(OPERATOR_TAG) "$(REGISTRY)/k8s-controller:$(OPERATOR_TAG)" - docker push "$(REGISTRY)/k8s-controller:$(OPERATOR_TAG)" + cd apptest/tester \ + && docker build --tag "$(TESTER_IMAGE)" . + docker push "$(TESTER_IMAGE)" @touch "$@" diff --git a/apptest/tester/tests/basic-suite.yaml b/apptest/tester/tests/basic-suite.yaml index 1c778bd..c216467 100644 --- a/apptest/tester/tests/basic-suite.yaml +++ b/apptest/tester/tests/basic-suite.yaml @@ -15,22 +15,12 @@ actions: #TODO test and write new tests expect: exitCode: equals: 0 -- name: Waiting for RedisEnterpriseClusters CRDs created - bashTest: - script: | - timeout 120 bash -c ' - until kubectl get crd redisenterpriseclusters.app.redislabs.com; - do echo "Waiting for RedisEnterpriseClusters CRDs created"; sleep 5; - done' - expect: - exitCode: - equals: 0 -- name: Waiting for redis-operator deployment to be created +- name: Waiting for redis-enterprise-operator deployment to be created bashTest: script: | timeout 120 bash -c ' - until kubectl get deployment -n ${NAMESPACE} | grep redis-operator - do echo "Waiting for redis-operator deployment to be created"; sleep 5; + until kubectl get deployment -n ${NAMESPACE} | grep redis-enterprise-operator + do echo "Waiting for redis-enterprise-operator deployment to be created"; sleep 5; done' expect: exitCode: diff --git a/chart/redis-operator/templates/job/crd-create.yaml b/chart/redis-operator/templates/job/crd-create.yaml index 1c9d9dc..1c373fa 100644 --- a/chart/redis-operator/templates/job/crd-create.yaml +++ b/chart/redis-operator/templates/job/crd-create.yaml @@ -29,7 +29,7 @@ spec: mountPath: /crd_to_create/ dnsPolicy: ClusterFirst restartPolicy: Never - serviceAccountName: {{ .Values.CDRJobServiceAccount }} + serviceAccountName: {{ .Values.CRDJobServiceAccount }} volumes: - name: crd-configmap configMap: diff --git a/deployer/Dockerfile b/deployer/Dockerfile index a6ea806..1f5a861 100644 --- a/deployer/Dockerfile +++ b/deployer/Dockerfile @@ -16,7 +16,6 @@ RUN cd /tmp/test \ ADD schema.yaml /tmp/schema.yaml -# Provide registry prefix and tag for default values for images. ARG REGISTRY ARG TAG @@ -25,17 +24,17 @@ RUN cat /tmp/schema.yaml \ > /tmp/schema.yaml.new \ && mv /tmp/schema.yaml.new /tmp/schema.yaml -#ADD apptest/deployer/schema.yaml /tmp/apptest/schema.yaml -#RUN cat /tmp/apptest/schema.yaml \ -# | env -i "REGISTRY=$REGISTRY" "TAG=$TAG" envsubst \ -# > /tmp/apptest/schema.yaml.new \ -# && mv /tmp/apptest/schema.yaml.new /tmp/apptest/schema.yaml +ADD apptest/deployer/schema.yaml /tmp/apptest/schema.yaml +RUN cat /tmp/apptest/schema.yaml \ + | env -i "REGISTRY=$REGISTRY" "TAG=$TAG" envsubst \ + > /tmp/apptest/schema.yaml.new \ + && mv /tmp/apptest/schema.yaml.new /tmp/apptest/schema.yaml FROM gcr.io/cloud-marketplace-tools/k8s/deployer_helm:$MARKETPLACE_TOOLS_TAG ARG CHART_NAME COPY --from=build /tmp/$CHART_NAME.tar.gz /data/chart/ -#COPY --from=build /tmp/test/$CHART_NAME.tar.gz /data-test/chart/ -#COPY --from=build /tmp/apptest/schema.yaml /data-test/ +COPY --from=build /tmp/test/$CHART_NAME.tar.gz /data-test/chart/ +COPY --from=build /tmp/apptest/schema.yaml /data-test/ COPY --from=build /tmp/schema.yaml /data/ diff --git a/schema.yaml b/schema.yaml index 385fdab..e485668 100644 --- a/schema.yaml +++ b/schema.yaml @@ -113,8 +113,11 @@ properties: description: Service account roles: - type: ClusterRole - rulesType: PREDEFINED - rulesFromRoleName: cluster-admin + rulesType: CUSTOM + rules: + - apiGroups: ["app.redislabs.com", "apiextensions.k8s.io"] + resources: ["*"] + verbs: ["*"] operator.replicas: type: integer title: Number of Cluster Nodes @@ -159,7 +162,7 @@ properties: default: 15 minimum: 1 maximum: 269 - CDRJobServiceAccount: + CRDJobServiceAccount: type: string title: Service account used by CRDs deployer x-google-marketplace: From c5733a6eb8514b4a98ad48cfdc178f7f4e717301 Mon Sep 17 00:00:00 2001 From: Alex Milowski Date: Wed, 14 Apr 2021 16:30:10 -0700 Subject: [PATCH 075/120] fixed repo --- upgrade.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/upgrade.sh b/upgrade.sh index 37143cb..aec4ee1 100755 --- a/upgrade.sh +++ b/upgrade.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash RELEASE=${1:-6.001205} -REPO=${2:-gcr.io/proven-reality-226706/redislabs} +REPO=${2:-gcr.io/cloud-marketplace/redislabs-public/redis-enterprise) SA=$3 RELEASE_SUFFIX=$(echo $RELEASE | sed s/\\./-/g) From 7a14821b9fbe1c218688dbea808ea8ef2b08bfdf Mon Sep 17 00:00:00 2001 From: Sambasiva Andaluri Date: Thu, 15 Apr 2021 18:19:03 -0400 Subject: [PATCH 076/120] add test case. fix app deletion --- apptest/tester/tests/basic-suite.yaml | 16 ++++++++++++++-- chart/redis-operator/templates/application.yaml | 5 +++++ chart/redis-operator/templates/configmap/cr.yaml | 4 ++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/apptest/tester/tests/basic-suite.yaml b/apptest/tester/tests/basic-suite.yaml index c216467..abfb383 100644 --- a/apptest/tester/tests/basic-suite.yaml +++ b/apptest/tester/tests/basic-suite.yaml @@ -1,5 +1,5 @@ -actions: #TODO test and write new tests -- name: kubectl smoke test +actions: +- name: kubectl version check bashTest: script: kubectl version expect: @@ -25,3 +25,15 @@ actions: #TODO test and write new tests expect: exitCode: equals: 0 +- name: Waiting for redis enterprise cluster to be running + bashTest: + script: | + timeout 600 bash -c ' + STATE=" " + until [ "$STATE" = "Running" ]; + do + echo "Waiting for redis enterprise cluster to be running"; STATE=$(kubectl get rec/redis-enterprise -o jsonpath='{.status.state}') ; sleep 5; + done' + expect: + exitCode: + equals: 0 diff --git a/chart/redis-operator/templates/application.yaml b/chart/redis-operator/templates/application.yaml index 7ff6b29..33a84b0 100644 --- a/chart/redis-operator/templates/application.yaml +++ b/chart/redis-operator/templates/application.yaml @@ -42,3 +42,8 @@ spec: kind: Service - group: apps/v1 kind: StatefulSet + - group: v1 + kind: PersistentVolumeClaim + - group: app.redislabs.com/v1 + kind: RedisEnterpriseCluster + \ No newline at end of file diff --git a/chart/redis-operator/templates/configmap/cr.yaml b/chart/redis-operator/templates/configmap/cr.yaml index 06bb848..3da706d 100644 --- a/chart/redis-operator/templates/configmap/cr.yaml +++ b/chart/redis-operator/templates/configmap/cr.yaml @@ -15,6 +15,10 @@ data: labels: app.kubernetes.io/name: "{{ .Release.Name }}" spec: + extraLabels: + app.kubernetes.io/name: "{{ .Release.Name }}" + podAnnotations: + app.kubernetes.io/name: "{{ .Release.Name }}" nodes: {{ .Values.operator.replicas }} persistentSpec: enabled: true From ee30d50239a786e77a767d3177924348be7d55b4 Mon Sep 17 00:00:00 2001 From: Sambasiva Andaluri Date: Sat, 17 Jul 2021 18:32:55 -0400 Subject: [PATCH 077/120] update to new release --- Makefile | 6 +-- chart/redis-operator/files/crd/rec_crd.yaml | 38 ++++++++++++++++--- chart/redis-operator/files/crd/redb_crd.yaml | 29 +++++++++++++- .../templates/service/admission-service.yaml | 11 ++++++ schema.yaml | 6 +-- 5 files changed, 78 insertions(+), 12 deletions(-) create mode 100644 chart/redis-operator/templates/service/admission-service.yaml diff --git a/Makefile b/Makefile index e56295d..9c1412d 100644 --- a/Makefile +++ b/Makefile @@ -20,17 +20,17 @@ $(info ---- REGISTRY = $(REGISTRY)) CHART_NAME := redis-operator $(info ---- CHART_NAME = $(CHART_NAME)) -REDIS_TAG ?= 6.0.12-57 +REDIS_TAG ?= 6.0.20-97 $(info ---- REDIS_TAG = $(REDIS_TAG)) -OPERATOR_TAG ?= 6.0.12-5 +OPERATOR_TAG ?= 6.0.20-12 $(info ---- OPERATOR_TAG = $(OPERATOR_TAG)) # Deployer tag is used for displaying versions in partner portal. # This version only support major.minor so the Redis version major.minor.patch # is converted into more readable form of major.2 digit zero padded minor + patch # without the hyphen -DEPLOYER_TAG ?= 6.001205 +DEPLOYER_TAG ?= 6.002012 $(info ---- DEPLOYER_TAG = $(DEPLOYER_TAG)) # Tag the deployer image with modified version. diff --git a/chart/redis-operator/files/crd/rec_crd.yaml b/chart/redis-operator/files/crd/rec_crd.yaml index 9788a7a..92de32d 100644 --- a/chart/redis-operator/files/crd/rec_crd.yaml +++ b/chart/redis-operator/files/crd/rec_crd.yaml @@ -39,14 +39,14 @@ spec: scope: Namespaced subresources: status: {} - version: v1alpha1 + version: v1 versions: - - name: v1alpha1 - served: true - storage: true - name: v1 served: true storage: false + - name: v1alpha1 + served: true + storage: true validation: openAPIV3Schema: description: RedisEnterpriseCluster is the Schema for the redisenterpriseclusters @@ -135,6 +135,21 @@ spec: value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' type: object type: object + clusterCredentialSecretName: + description: Secret Name/Path to use for Cluster Credentials. If left + blank, will use cluster name + type: string + clusterCredentialSecretRole: + description: Used only if ClusterCredentialSecretType is vault, to define + vault role to be used. If blank, defaults to "redis-enterprise-operator" + type: string + clusterCredentialSecretType: + description: Type of Secret to use for ClusterCredential, Vault, Kuberetes,... + If left blank, will default ot kubernetes secrets + enum: + - vault + - kubernetes + type: string clusterRecovery: description: ClusterRecovery initiates cluster recovery when set to true. Note that this field is cleared automatically after the cluster @@ -433,6 +448,20 @@ spec: serviceAccountName: description: Name of the service account to use type: string + redisEnterpriseServicesConfiguration: + description: Cluster optional services settings + properties: + mdnsServer: + properties: + operatingMode: + enum: + - enabled + - disabled + type: string + required: + - operatingMode + type: object + type: object servicesRiggerSpec: description: Specification for service rigger properties: @@ -1703,4 +1732,3 @@ spec: type: array type: object type: object - diff --git a/chart/redis-operator/files/crd/redb_crd.yaml b/chart/redis-operator/files/crd/redb_crd.yaml index 6965ad4..fd870b1 100644 --- a/chart/redis-operator/files/crd/redb_crd.yaml +++ b/chart/redis-operator/files/crd/redb_crd.yaml @@ -3,6 +3,28 @@ kind: CustomResourceDefinition metadata: name: redisenterprisedatabases.app.redislabs.com spec: + additionalPrinterColumns: + - JSONPath: .status.version + name: Version + type: string + - JSONPath: .status.internalEndpoints[*].port + name: Port + type: string + - JSONPath: .status.redisEnterpriseCluster + name: Cluster + type: string + - JSONPath: .status.shardStatuses.active + name: Shards + type: string + - JSONPath: .status.status + name: Status + type: string + - JSONPath: .status.specStatus + name: Spec Status + type: string + - JSONPath: .metadata.creationTimestamp + name: Age + type: date group: app.redislabs.com names: kind: RedisEnterpriseDatabase @@ -327,6 +349,12 @@ spec: defaultUser: description: Is connecting with a default user allowed? type: boolean + ossCluster: + description: OSS Cluster mode option + type: boolean + proxyPolicy: + description: The policy used for proxy binding to the endpoint + type: string evictionPolicy: description: Database eviction policy. see more https://docs.redislabs.com/latest/rs/administering/database-operations/eviction-policy/ type: string @@ -547,4 +575,3 @@ spec: type: string type: object type: object - diff --git a/chart/redis-operator/templates/service/admission-service.yaml b/chart/redis-operator/templates/service/admission-service.yaml new file mode 100644 index 0000000..200987f --- /dev/null +++ b/chart/redis-operator/templates/service/admission-service.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: admission +spec: + ports: + - port: 443 + protocol: TCP + targetPort: 8443 + selector: + name: redis-enterprise-operator diff --git a/schema.yaml b/schema.yaml index e485668..a2db6c2 100644 --- a/schema.yaml +++ b/schema.yaml @@ -110,7 +110,7 @@ properties: x-google-marketplace: type: SERVICE_ACCOUNT serviceAccount: - description: Service account + description: Service account for Redis Operator roles: - type: ClusterRole rulesType: CUSTOM @@ -121,7 +121,7 @@ properties: operator.replicas: type: integer title: Number of Cluster Nodes - description: The number of Pods in the solution + description: Number of Cluster Nodes default: 3 minimum: 3 maximum: 11 @@ -168,7 +168,7 @@ properties: x-google-marketplace: type: SERVICE_ACCOUNT serviceAccount: - description: Service account + description: Service account used by CRDs deployer roles: - type: ClusterRole rulesType: CUSTOM From 8f9a62a548b0fe5ebf373b21e2d099f29e322853 Mon Sep 17 00:00:00 2001 From: Sambasiva Andaluri Date: Tue, 20 Jul 2021 13:04:48 -0400 Subject: [PATCH 078/120] fix values to get template to work without errors --- chart/redis-operator/Chart.yaml | 4 ++-- chart/redis-operator/templates/deployment/operator.yaml | 1 + chart/redis-operator/values.yaml | 5 ++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/chart/redis-operator/Chart.yaml b/chart/redis-operator/Chart.yaml index 954a1a5..473d7f2 100644 --- a/chart/redis-operator/Chart.yaml +++ b/chart/redis-operator/Chart.yaml @@ -1,3 +1,3 @@ +apiVersion: "v2" name: redis-operator -version: 1.20 -# TODO update version +version: "1.20" diff --git a/chart/redis-operator/templates/deployment/operator.yaml b/chart/redis-operator/templates/deployment/operator.yaml index 9644db1..7c00146 100644 --- a/chart/redis-operator/templates/deployment/operator.yaml +++ b/chart/redis-operator/templates/deployment/operator.yaml @@ -81,3 +81,4 @@ spec: - name: ubb-configmap configMap: name: {{ template "redis_operator.UBBAgentConfigMap" . }} + diff --git a/chart/redis-operator/values.yaml b/chart/redis-operator/values.yaml index 9fc34f6..e685dc4 100644 --- a/chart/redis-operator/values.yaml +++ b/chart/redis-operator/values.yaml @@ -6,6 +6,5 @@ operator: ubbagent: image: null usagemeter: - image: - repository: null - tag: null + image: null + tag: null From e48b5e82eb232be563d1b5f5aab8306ed9e04cbc Mon Sep 17 00:00:00 2001 From: Sambasiva Andaluri Date: Thu, 29 Jul 2021 10:19:09 -0400 Subject: [PATCH 079/120] cleanup manual install instructions --- BUILD.md | 156 +++++++++++++++ README.md | 185 +++++++++++++----- .../redis-operator/templates/application.yaml | 4 +- images/license-1.png | Bin 0 -> 160726 bytes images/license-2.png | Bin 0 -> 168276 bytes schema.yaml | 12 +- 6 files changed, 300 insertions(+), 57 deletions(-) create mode 100644 BUILD.md create mode 100644 images/license-1.png create mode 100644 images/license-2.png diff --git a/BUILD.md b/BUILD.md new file mode 100644 index 0000000..42faa1d --- /dev/null +++ b/BUILD.md @@ -0,0 +1,156 @@ +# Overview + +This repo is for building and deploying [Redis Enterprise](https://github.com/RedisLabs/redis-enterprise-k8s-docs) for GKE Market Place. + +## Design + +### Solution Information + +Redis-Enterprise cluster is deployed within a Kubernetes StatefulSet. + +The deployment creates two services: + +- A client-facing one, designed to be used for client connections to the Redis-Enterprise + cluster with port forwarding or using a LoadBalancer, +- Service discovery: a headless service for connections between + the Redis-Enterprise nodes. + +# Build instructions + +## Quick install with Google Cloud Marketplace + +Get up and running with a few clicks! Install this Redis Enterprise app to a +Google Kubernetes Engine cluster using Google Cloud Marketplace. You can do this from the Applications tab in the GKE page in the Cloud Console. + +### Prerequisites + +#### Set up command-line tools + +You'll need the following tools in your development environment: + +- [gcloud](https://cloud.google.com/sdk/gcloud/) +- [kubectl](https://kubernetes.io/docs/reference/kubectl/overview/) +- [docker](https://docs.docker.com/install/) +- [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) +- [marketplace tools](https://github.com/GoogleCloudPlatform/marketplace-k8s-app-tools/blob/master/docs/tool-prerequisites.md) + +Configure `gcloud` as a Docker credential helper: + +```shell +gcloud auth login +gcloud auth configure-docker +``` + +#### Permissions + +User who builds and deploys the solution would need "Kubernetes Engine Admin" and "Editor" permissions. + +#### Create a Google Kubernetes Engine cluster + +Create a new cluster from the command line. The command is idempotent so runs after the first are not needed, but do no harm. + +```shell +export CLUSTER=redis-cluster +export ZONE=us-west1-a +gcloud container clusters create "$CLUSTER" --zone "$ZONE" +``` + +Configure `kubectl` to connect to the new cluster. The following is optional as by default cluster create adds credentials to local kube config. + +```shell +gcloud container clusters get-credentials "$CLUSTER" --zone "$ZONE" +``` +Create a namespace where redis cluster and database should be created + +```shell +kubectl create namespace redis +``` + +#### Clone the repos + + +```shell +git clone https://github.com/GoogleCloudPlatform/click-to-deploy +cd k8s +git clone https://github.com/RedisLabs/gkemarketplace +``` + +Optional: For reference, you can get RedisLabs Enterprise K8s Operator code (i.e., unrelated to Google MP) + +```shell +git clone https://github.com/RedisLabs/redis-enterprise-k8s-docs.git +``` + +Optional: For reference, you can get MP K8s tools, examples, and instructions + +```shell +git clone https://github.com/GoogleCloudPlatform/marketplace-k8s-app-tools +``` + +#### Building + +```shell +cd gkemarketplace +make clean +make app/build +``` + +#### Deploying to GKE + +An Application resource is an addition to the Kubernetes metamodel: A collection of individual Kubernetes components, such as Services, Deployments, etc, that you can manage as a group. + +The Application resource is defined by the [Kubernetes SIG-apps](https://github.com/kubernetes/community/tree/master/sig-apps) community. The source code can be found on +[github.com/kubernetes-sigs/application](https://github.com/kubernetes-sigs/application). + + +```shell +make crd/install +mpdev install --deployer= --parameters='{"name": "redis-enterprise-operator", "namespace": "redis", "operator.nodeCpu": 5000, "operator.nodeMem": 16, "reportingSecret": "gs://cloud-marketplace-tools/reporting_secrets/fake_reporting_secret.yaml"} +``` + +#### View the app in the Google Cloud Platform Console + +Get the Google Cloud Console URL for your app, then open this URL in your browser: + +```shell +echo "https://console.cloud.google.com/kubernetes/application/${ZONE}/${CLUSTER}/${NAMESPACE}/${APP_INSTANCE_NAME}" +``` + +#### Get the status of the cluster + +By default, the application does not have an external IP address. Use `kubectl port-forward` to access the dashboard on the master +node at `localhost`. + +``` +kubectl port-forward redis-enterprise-cluster-0 8443 + +``` + +#### Getting the Admin Password + +See [instructions here](https://docs.redislabs.com/latest/rs/faqs/) under "How to retrieve the username/password for a Redis Enterprise Cluster?" + +In summary, `kubectl get secret redis-enterprise -o yaml|grep password|cut -d':' -f 2|base64 --decode` should get you the password, and you should already know the username (default `admin@example.com`) + +#### Access the Redis-Enterprise service externally + +``` +kubectl get services -n $NAMESPACE +``` + +**NOTE:** + +1. It might take some time for the external IP to be provisioned. +2. This works out-of-the-box in GKE but not in Anthos, where special measures are needed to configure the Load Balancer. + + +# Uninstall the Application + +## Using the Google Cloud Platform Console + +1. In the GCP Console, open [Kubernetes Applications](https://console.cloud.google.com/kubernetes/application). + +1. From the list of applications, click **Redis-Enterprise**. + +1. On the Application Details page, click **Delete**. + diff --git a/README.md b/README.md index 42faa1d..1d6aade 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,15 @@ # Overview -This repo is for building and deploying [Redis Enterprise](https://github.com/RedisLabs/redis-enterprise-k8s-docs) for GKE Market Place. +This document describes installation process for [Redis Enterprise](https://github.com/RedisLabs/redis-enterprise-k8s-docs) via GKE Market Place. There are two ways to install Redis Enterprise in GKE, one using GKE Console UI or using CLI. -## Design - -### Solution Information - -Redis-Enterprise cluster is deployed within a Kubernetes StatefulSet. - -The deployment creates two services: - -- A client-facing one, designed to be used for client connections to the Redis-Enterprise - cluster with port forwarding or using a LoadBalancer, -- Service discovery: a headless service for connections between - the Redis-Enterprise nodes. - -# Build instructions - -## Quick install with Google Cloud Marketplace +## Quick install with Google Cloud Marketplace (from GKE Console) Get up and running with a few clicks! Install this Redis Enterprise app to a Google Kubernetes Engine cluster using Google Cloud Marketplace. You can do this from the Applications tab in the GKE page in the Cloud Console. -### Prerequisites +## Manual install (CLI) -#### Set up command-line tools +### Set up command-line tools You'll need the following tools in your development environment: @@ -32,7 +17,6 @@ You'll need the following tools in your development environment: - [kubectl](https://kubernetes.io/docs/reference/kubectl/overview/) - [docker](https://docs.docker.com/install/) - [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) -- [marketplace tools](https://github.com/GoogleCloudPlatform/marketplace-k8s-app-tools/blob/master/docs/tool-prerequisites.md) Configure `gcloud` as a Docker credential helper: @@ -41,22 +25,36 @@ gcloud auth login gcloud auth configure-docker ``` -#### Permissions +### Obtain license key from GKE Marketplace + +#### Purchase Redis Enterprise on GKE via GCP MP in a GCP project + +See the following screenshot for an example + +![Purchase Redis Enterprise](images/license-1.png) + +#### Generate a license file for the GCP project for the subscription entitlement for billing + +See the following screenshot for an example. Click the “Generate license key” button to generate the license key. It will automatically be downloaded onto your computer. + +![Get License file for Redis Enterprise](images/license-2.png) + +Save the license key file preferably as ```license-key.yaml```. + +### Permissions User who builds and deploys the solution would need "Kubernetes Engine Admin" and "Editor" permissions. -#### Create a Google Kubernetes Engine cluster +### Create (or use an existing) Google Kubernetes Engine cluster -Create a new cluster from the command line. The command is idempotent so runs after the first are not needed, but do no harm. +Redis Enterprise requires at least 5 CPUs and 16GB RAM for each worker node. If you already have an existing cluster that matches the CPU and memory requirements, then skip this step. If not create a new GKE cluster that matches this specification. See below for an example ```shell export CLUSTER=redis-cluster export ZONE=us-west1-a -gcloud container clusters create "$CLUSTER" --zone "$ZONE" +gcloud container clusters create $CLUSTER --zone $ZONE --machine-type n2-standard-8 ``` -Configure `kubectl` to connect to the new cluster. The following is optional as by default cluster create adds credentials to local kube config. - ```shell gcloud container clusters get-credentials "$CLUSTER" --zone "$ZONE" ``` @@ -66,49 +64,140 @@ Create a namespace where redis cluster and database should be created kubectl create namespace redis ``` -#### Clone the repos +```shell +kubectl config set-context --current --namespace=redis +``` +### Clone the repo ```shell -git clone https://github.com/GoogleCloudPlatform/click-to-deploy -cd k8s git clone https://github.com/RedisLabs/gkemarketplace ``` -Optional: For reference, you can get RedisLabs Enterprise K8s Operator code (i.e., unrelated to Google MP) + +### Prepare Environment variables + +--- +**NOTE** + +Redis version tags are in the format Major.Minor.Patch-Sub but GKE Marketplace requires only Major.Minor, so in order to convert Redis version label to Marketplace use zero padded minor, patch and sub. For example: Redis version 6.0.12-05 would become Marketplace version (DEPLOYER_TAG) 0.001205 + +--- ```shell -git clone https://github.com/RedisLabs/redis-enterprise-k8s-docs.git +export APP_INSTANCE_NAME=redis-enterprise-operator +export NAMESPACE=redis +export TAG=6.0.20-12 +export DEPLOYER_TAG=6.002012 +export REPO=gcr.io/cloud-marketplace/redislabs-public/redis-enterprise ``` -Optional: For reference, you can get MP K8s tools, examples, and instructions +### Prepare License Key -```shell -git clone https://github.com/GoogleCloudPlatform/marketplace-k8s-app-tools +The license key is a kubernetes secret, add a metadata.name for set to application instance name (see APP_INSTANCE_NAME env variable) with a suffix “-reportingsecret” (exactly). For example, if instance name is ```redis-enterprise-operator``` then reporting secret ```metadata.name``` must be ```redis-enterprise-operator-reportingsecret```. + + +For Development and Testing (get fake_reporting_secret.yaml from GCS first). +gsutil cp gs://cloud-marketplace-tools/reporting_secrets/fake_reporting_secret.yaml . + + +For Development (fake_reporting_secret.yaml) or Production (license-key.yaml) add the following to yaml after the apiVersion line + +```yaml +metadata: + name: redis-enterprise-operator-reportingsecret ``` -#### Building +### Create Roles, RoleBindings and ServiceAccount + +Create namespaced roles (for the redis namespace ) and service account. Note the service account name, which you need for creating cluster roles. +``` +kubectl apply -f https://raw.githubusercontent.com/RedisLabs/redis-enterprise-k8s-docs/master/role.yaml +kubectl apply -f https://raw.githubusercontent.com/RedisLabs/redis-enterprise-k8s-docs/master/role_binding.yaml +kubectl apply -f https://raw.githubusercontent.com/RedisLabs/redis-enterprise-k8s-docs/master/service_account.yaml +``` + +Create a cluster role for creating cluster scoped custom resources and checking their status. Use the spec below and save it in ```cluster-role.yaml``` + +```yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: redis-operator-cluster-role +rules: +- apiGroups: ["apiextensions.k8s.io"] + resources: ["customresourcedefinitions"] + verbs: ["get", "create", "list", "patch"] +- apiGroups: ["app.redislabs.com", "apiextensions.k8s.io"] + resources: ["*"] + verbs: ["*"] +``` + +Create cluster role binding e.g. ```cluster-role-binding.yaml``` + +```yaml +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: redis-operator-cluster-role-binding +subjects: +- kind: ServiceAccount + name: default + namespace: redis +- kind: ServiceAccount + name: redis-enterprise-operator + namespace: redis +roleRef: + kind: ClusterRole + name: redis-operator-cluster-role + apiGroup: rbac.authorization.k8s.io +``` + +Apply the cluster-role and cluster-role-binding + +```yaml +kubectl apply -f cluster-role.yaml +kubectl apply -f cluster-role-binding.yaml +``` + +### Install the Application CRD + +GKE marketplace integration uses Application resource to make easier to manage RedisEnterprise resources as a single application. ```shell -cd gkemarketplace -make clean -make app/build +kubectl apply -f https://raw.githubusercontent.com/GoogleCloudPlatform/marketplace-k8s-app-tools/master/crd/app-crd.yaml ``` -#### Deploying to GKE +### Generate bundle from helm + +Run the helm template to generate operator deployment. -An Application resource is an addition to the Kubernetes metamodel: A collection of individual Kubernetes components, such as Services, Deployments, etc, that you can manage as a group. +```shell +helm template "$APP_INSTANCE_NAME" chart/redis-operator \ + --namespace "$NAMESPACE" \ + --set deployerHelm.image="$REPO/deployer:$DEPLOYER_TAG" \ + --set operator.serviceAccountName="redis-enterprise-operator" \ + --set operator.image.repository="$REPO" \ + --set operator.image.tag="$TAG" \ + --set usagemeter.image="$REPO/usagemeter" \ + --set usagemeter.tag="$TAG" \ + --set reportingSecret="$APP_INSTANCE_NAME-reportingsecret" \ + --set ingressAvailable=”True” \ + --set operator.nodeMem=16 \ + --set operator.nodeCpu=5000 \ + --set operator.redisAdmin=sam@doit-intl.com \ + --set operator.replicas=3 \ + --set operator.storageClass=standard > redis-bundle.yaml +``` -The Application resource is defined by the [Kubernetes SIG-apps](https://github.com/kubernetes/community/tree/master/sig-apps) community. The source code can be found on -[github.com/kubernetes-sigs/application](https://github.com/kubernetes-sigs/application). +Apply the generated yaml. ```shell -make crd/install -mpdev install --deployer= --parameters='{"name": "redis-enterprise-operator", "namespace": "redis", "operator.nodeCpu": 5000, "operator.nodeMem": 16, "reportingSecret": "gs://cloud-marketplace-tools/reporting_secrets/fake_reporting_secret.yaml"} +kubectl apply -f redis-bundle.yaml ``` -#### View the app in the Google Cloud Platform Console +### View the app in the Google Cloud Platform Console Get the Google Cloud Console URL for your app, then open this URL in your browser: @@ -116,7 +205,7 @@ Get the Google Cloud Console URL for your app, then open this URL in your browse echo "https://console.cloud.google.com/kubernetes/application/${ZONE}/${CLUSTER}/${NAMESPACE}/${APP_INSTANCE_NAME}" ``` -#### Get the status of the cluster +### Get the status of the cluster By default, the application does not have an external IP address. Use `kubectl port-forward` to access the dashboard on the master node at `localhost`. @@ -126,13 +215,13 @@ kubectl port-forward redis-enterprise-cluster-0 8443 ``` -#### Getting the Admin Password +### Getting the Admin Password See [instructions here](https://docs.redislabs.com/latest/rs/faqs/) under "How to retrieve the username/password for a Redis Enterprise Cluster?" In summary, `kubectl get secret redis-enterprise -o yaml|grep password|cut -d':' -f 2|base64 --decode` should get you the password, and you should already know the username (default `admin@example.com`) -#### Access the Redis-Enterprise service externally +### Access the Redis-Enterprise service externally ``` kubectl get services -n $NAMESPACE diff --git a/chart/redis-operator/templates/application.yaml b/chart/redis-operator/templates/application.yaml index 33a84b0..bc74fa7 100644 --- a/chart/redis-operator/templates/application.yaml +++ b/chart/redis-operator/templates/application.yaml @@ -21,7 +21,7 @@ spec: - description: 'User Guide: Redis Enterprise' url: https://support.redislabs.com notes: |- - See more details and manual installation instructions here https://github.com/RedisLabs/gkemarketplace + See more details and manual installation instructions here https://github.com/RedisLabs/gkemarketplace/README.md selector: matchLabels: app.kubernetes.io/name: "{{ .Release.Name }}" @@ -32,8 +32,6 @@ spec: kind: ConfigMap - group: v1 kind: ServiceAccount - - group: v1 - kind: Job - group: v1 kind: Pod - group: v1 diff --git a/images/license-1.png b/images/license-1.png new file mode 100644 index 0000000000000000000000000000000000000000..21e912adc48a30178ac414dd0d1439e17436b149 GIT binary patch literal 160726 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ⅇ2xe{1mS0vS2}dr9KV@Gb1lg~8fi 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 ^+8O`>Vz7jqV2=jiy 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 literal 0 HcmV?d00001 diff --git a/schema.yaml b/schema.yaml index a2db6c2..ff21b43 100644 --- a/schema.yaml +++ b/schema.yaml @@ -39,11 +39,11 @@ properties: type: NAMESPACE operator.serviceAccountName: type: string - title: Service account used by Redis Operator + title: Service account for operator x-google-marketplace: type: SERVICE_ACCOUNT serviceAccount: - description: Service account + description: Service account for operator roles: - type: ClusterRole rulesType: CUSTOM @@ -106,11 +106,11 @@ properties: verbs: ["*"] CRJobServiceAccount: type: string - title: Service account used by Redis Operator + title: Service account for custom resources x-google-marketplace: type: SERVICE_ACCOUNT serviceAccount: - description: Service account for Redis Operator + description: Service account for custom resources roles: - type: ClusterRole rulesType: CUSTOM @@ -164,11 +164,11 @@ properties: maximum: 269 CRDJobServiceAccount: type: string - title: Service account used by CRDs deployer + title: Service account for custom resource definitions x-google-marketplace: type: SERVICE_ACCOUNT serviceAccount: - description: Service account used by CRDs deployer + description: Service account for custom resource definitions roles: - type: ClusterRole rulesType: CUSTOM From d5995e66ff3cf9864d23da416e8a59782bd1ad3d Mon Sep 17 00:00:00 2001 From: Sambasiva Andaluri Date: Thu, 29 Jul 2021 18:27:14 -0400 Subject: [PATCH 080/120] fix a typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1d6aade..0b556a5 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ git clone https://github.com/RedisLabs/gkemarketplace --- **NOTE** -Redis version tags are in the format Major.Minor.Patch-Sub but GKE Marketplace requires only Major.Minor, so in order to convert Redis version label to Marketplace use zero padded minor, patch and sub. For example: Redis version 6.0.12-05 would become Marketplace version (DEPLOYER_TAG) 0.001205 +Redis version tags are in the format Major.Minor.Patch-Sub but GKE Marketplace requires only Major.Minor, so in order to convert Redis version label to Marketplace use zero padded minor, patch and sub. For example: Redis version 6.0.12-05 would become Marketplace version (DEPLOYER_TAG) 6.001205 --- From 31e2306746885633e74961851fd3163bcd7c032b Mon Sep 17 00:00:00 2001 From: Sambasiva Andaluri Date: Thu, 29 Jul 2021 18:33:32 -0400 Subject: [PATCH 081/120] fix markdown --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0b556a5..1f5520c 100644 --- a/README.md +++ b/README.md @@ -97,9 +97,11 @@ export REPO=gcr.io/cloud-marketplace/redislabs-public/redis-enterprise The license key is a kubernetes secret, add a metadata.name for set to application instance name (see APP_INSTANCE_NAME env variable) with a suffix “-reportingsecret” (exactly). For example, if instance name is ```redis-enterprise-operator``` then reporting secret ```metadata.name``` must be ```redis-enterprise-operator-reportingsecret```. -For Development and Testing (get fake_reporting_secret.yaml from GCS first). -gsutil cp gs://cloud-marketplace-tools/reporting_secrets/fake_reporting_secret.yaml . +For Development and Testing (get fake_reporting_secret.yaml from GCS first) and then modify the metadata.name +```shell +gsutil cp gs://cloud-marketplace-tools/reporting_secrets/fake_reporting_secret.yaml . +``` For Development (fake_reporting_secret.yaml) or Production (license-key.yaml) add the following to yaml after the apiVersion line From 8201540361d659f868e3a9b705f20fee3e95e718 Mon Sep 17 00:00:00 2001 From: Sambasiva Andaluri Date: Thu, 5 Aug 2021 11:18:27 -0400 Subject: [PATCH 082/120] Tweak readme fix typos and cleanup --- README.md | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 1f5520c..0419e7b 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ # Overview -This document describes installation process for [Redis Enterprise](https://github.com/RedisLabs/redis-enterprise-k8s-docs) via GKE Market Place. There are two ways to install Redis Enterprise in GKE, one using GKE Console UI or using CLI. +This document describes the installation process for [Redis Enterprise](https://github.com/RedisLabs/redis-enterprise-k8s-docs) via Google Cloud Marketplace. There are two ways to install Redis Enterprise in GKE, one using GCP Console UI and using CLI. ## Quick install with Google Cloud Marketplace (from GKE Console) Get up and running with a few clicks! Install this Redis Enterprise app to a -Google Kubernetes Engine cluster using Google Cloud Marketplace. You can do this from the Applications tab in the GKE page in the Cloud Console. +Google Kubernetes Engine cluster using Google Cloud Marketplace. You can do this from the Applications tab in the [GKE listing page](https://console.cloud.google.com/marketplace/product/redislabs-public/redis-enterprise) in the Cloud Console. ## Manual install (CLI) @@ -17,6 +17,7 @@ You'll need the following tools in your development environment: - [kubectl](https://kubernetes.io/docs/reference/kubectl/overview/) - [docker](https://docs.docker.com/install/) - [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) +- [helm](https://helm.sh/docs/intro/install/) Configure `gcloud` as a Docker credential helper: @@ -25,7 +26,7 @@ gcloud auth login gcloud auth configure-docker ``` -### Obtain license key from GKE Marketplace +### Obtain license key from Google Cloud Marketplace #### Purchase Redis Enterprise on GKE via GCP MP in a GCP project @@ -43,11 +44,11 @@ Save the license key file preferably as ```license-key.yaml```. ### Permissions -User who builds and deploys the solution would need "Kubernetes Engine Admin" and "Editor" permissions. +Users who build and deploy the solution would need "Kubernetes Engine Admin" and "Editor" permissions. ### Create (or use an existing) Google Kubernetes Engine cluster -Redis Enterprise requires at least 5 CPUs and 16GB RAM for each worker node. If you already have an existing cluster that matches the CPU and memory requirements, then skip this step. If not create a new GKE cluster that matches this specification. See below for an example +Redis Enterprise requires at least 5 CPUs and 16GB RAM for each worker node. If you already have an existing cluster that matches the CPU and memory requirements, then skip this step. If not, create a new GKE cluster that matches this specification. See below for an example ```shell export CLUSTER=redis-cluster @@ -94,7 +95,7 @@ export REPO=gcr.io/cloud-marketplace/redislabs-public/redis-enterprise ### Prepare License Key -The license key is a kubernetes secret, add a metadata.name for set to application instance name (see APP_INSTANCE_NAME env variable) with a suffix “-reportingsecret” (exactly). For example, if instance name is ```redis-enterprise-operator``` then reporting secret ```metadata.name``` must be ```redis-enterprise-operator-reportingsecret```. +The license key is a Kubernetes secret, add a metadata.name for set to application instance name (see APP_INSTANCE_NAME env variable) with a suffix “-reportingsecret” (exactly). For example, if instance name is ```redis-enterprise-operator``` then reporting secret ```metadata.name``` must be ```redis-enterprise-operator-reportingsecret```. For Development and Testing (get fake_reporting_secret.yaml from GCS first) and then modify the metadata.name @@ -121,7 +122,8 @@ kubectl apply -f https://raw.githubusercontent.com/RedisLabs/redis-enterprise-k8 Create a cluster role for creating cluster scoped custom resources and checking their status. Use the spec below and save it in ```cluster-role.yaml``` -```yaml +```shell +cat < redis-bundle.yaml ``` - Apply the generated yaml. ```shell From 2595d10b04cd81f598fd2e1626a5283127010f79 Mon Sep 17 00:00:00 2001 From: Sambasiva Andaluri Date: Fri, 6 Aug 2021 15:35:38 -0400 Subject: [PATCH 083/120] fix makefile to merge to master --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 9c1412d..7d744d9 100644 --- a/Makefile +++ b/Makefile @@ -9,11 +9,11 @@ include ../gcloud.Makefile include ../var.Makefile # Production repo -#REGISTRY ?= marketplace.gcr.io/google/redis-enterprise-operator +REGISTRY ?= marketplace.gcr.io/google/redis-enterprise-operator # Artifact repo #REGISTRY := us-central1-docker.pkg.dev/proven-reality-226706/redis-market-place # Container repo -REGISTRY := gcr.io/proven-reality-226706/redislabs +#REGISTRY := gcr.io/proven-reality-226706/redislabs $(info ---- REGISTRY = $(REGISTRY)) From 54bc0831917b5854395642eeae79b5ae3c9608b3 Mon Sep 17 00:00:00 2001 From: Sambasiva Andaluri Date: Wed, 6 Oct 2021 13:55:33 -0400 Subject: [PATCH 084/120] Fix verification --- Makefile | 4 +- .../redis-operator/templates/tester.yaml | 2 +- apptest/tester/Dockerfile | 16 +++--- apptest/tester/tests/basic-suite.yaml | 32 ++++++----- chart/redis-operator/templates/_helpers.tpl | 21 +++++++ .../redis-operator/templates/application.yaml | 7 +-- .../templates/job/cr-patch.yaml | 57 +++++++++++++++++++ deployer/Dockerfile | 4 ++ dev-test-create.sh | 4 ++ dev-test-delete.sh | 2 + dev-test-install.sh | 2 + dev-test-verify.sh | 2 + schema.yaml | 13 ++++- 13 files changed, 134 insertions(+), 32 deletions(-) create mode 100644 chart/redis-operator/templates/job/cr-patch.yaml create mode 100755 dev-test-create.sh create mode 100755 dev-test-delete.sh create mode 100755 dev-test-install.sh create mode 100755 dev-test-verify.sh diff --git a/Makefile b/Makefile index 7d744d9..9c1412d 100644 --- a/Makefile +++ b/Makefile @@ -9,11 +9,11 @@ include ../gcloud.Makefile include ../var.Makefile # Production repo -REGISTRY ?= marketplace.gcr.io/google/redis-enterprise-operator +#REGISTRY ?= marketplace.gcr.io/google/redis-enterprise-operator # Artifact repo #REGISTRY := us-central1-docker.pkg.dev/proven-reality-226706/redis-market-place # Container repo -#REGISTRY := gcr.io/proven-reality-226706/redislabs +REGISTRY := gcr.io/proven-reality-226706/redislabs $(info ---- REGISTRY = $(REGISTRY)) diff --git a/apptest/deployer/redis-operator/templates/tester.yaml b/apptest/deployer/redis-operator/templates/tester.yaml index f733a01..3efe17b 100644 --- a/apptest/deployer/redis-operator/templates/tester.yaml +++ b/apptest/deployer/redis-operator/templates/tester.yaml @@ -25,7 +25,7 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace - - name: APP_INSTANCE_NAME + - name: NAME value: {{ .Release.Name }} initContainers: {{- include "initContainerWaitForCRDsDeploy" . | nindent 4 }} diff --git a/apptest/tester/Dockerfile b/apptest/tester/Dockerfile index 0eb4bcf..9112bc2 100644 --- a/apptest/tester/Dockerfile +++ b/apptest/tester/Dockerfile @@ -1,16 +1,16 @@ FROM gcr.io/cloud-marketplace-tools/testrunner:0.1.2 -RUN apt-get update && apt-get install -y --no-install-recommends \ +ENV WAIT_FOR_READY_TIMEOUT 3600 +ENV TESTER_TIMEOUT 3600 + +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ curl wget dnsutils netcat jq \ && rm -rf /var/lib/apt/lists/* -RUN mkdir -p /opt/kubectl/1.14 \ - && wget -q -O /opt/kubectl/1.14/kubectl \ - https://storage.googleapis.com/kubernetes-release/release/v1.14.8/bin/linux/amd64/kubectl \ - && chmod 755 /opt/kubectl/1.14/kubectl \ - && ln -s /opt/kubectl/1.14/kubectl /usr/bin/kubectl - - +RUN curl -Lo /usr/local/bin/kubectl https://dl.k8s.io/release/v1.22.0/bin/linux/amd64/kubectl +RUN chmod +x /usr/local/bin/kubectl + COPY tests/basic-suite.yaml /tests/basic-suite.yaml COPY tester.sh /tester.sh diff --git a/apptest/tester/tests/basic-suite.yaml b/apptest/tester/tests/basic-suite.yaml index abfb383..042dcd7 100644 --- a/apptest/tester/tests/basic-suite.yaml +++ b/apptest/tester/tests/basic-suite.yaml @@ -5,26 +5,18 @@ actions: expect: exitCode: equals: 0 -- name: Waiting for RedisEnterpriseClusters CRDs created +- name: Waiting for RedisEnterpriseClusters (and implicitly RedisEnterpriseDatabase) CRDs created bashTest: script: | - timeout 120 bash -c ' + timeout 300 bash -c ' until kubectl get crd redisenterpriseclusters.app.redislabs.com; do echo "Waiting for RedisEnterpriseClusters CRDs created"; sleep 5; - done' - expect: - exitCode: - equals: 0 -- name: Waiting for redis-enterprise-operator deployment to be created - bashTest: - script: | - timeout 120 bash -c ' - until kubectl get deployment -n ${NAMESPACE} | grep redis-enterprise-operator - do echo "Waiting for redis-enterprise-operator deployment to be created"; sleep 5; - done' + done + ' expect: exitCode: equals: 0 + - name: Waiting for redis enterprise cluster to be running bashTest: script: | @@ -32,8 +24,20 @@ actions: STATE=" " until [ "$STATE" = "Running" ]; do - echo "Waiting for redis enterprise cluster to be running"; STATE=$(kubectl get rec/redis-enterprise -o jsonpath='{.status.state}') ; sleep 5; + echo "Waiting for redis enterprise cluster to be running"; STATE=$(kubectl get -n ${NAMESPACE} rec/redis-enterprise -o jsonpath='{.status.state}') ; sleep 5; done' expect: exitCode: equals: 0 +- name: Explicitly delete REC to pass verification. + bashTest: + script: | + timeout 1200 bash -c ' + kubectl delete -n ${NAMESPACE} rec/redis-enterprise; + sleep 60; + kubectl delete -n ${NAMESPACE} deployment.apps/redis-enterprise-operator; + sleep 60 + ' + expect: + exitCode: + equals: 0 diff --git a/chart/redis-operator/templates/_helpers.tpl b/chart/redis-operator/templates/_helpers.tpl index 3d00dfe..bf30208 100644 --- a/chart/redis-operator/templates/_helpers.tpl +++ b/chart/redis-operator/templates/_helpers.tpl @@ -6,6 +6,10 @@ {{- printf "%s-crd-job" .Release.Name | trunc 63 -}} {{- end -}} +{{- define "redis_operator.CRPatchJob" -}} +{{- printf "%s-cr-patch-job" .Release.Name | trunc 63 -}} +{{- end -}} + {{- define "redis_operator.CRsConfigMap" -}} {{- printf "%s-cr-config-map" .Release.Name | trunc 63 -}} {{- end -}} @@ -37,3 +41,20 @@ name: wait-for-crds-created image: {{ .Values.deployerHelm.image }} {{- end -}} + +{{- define "initContainerWaitForCRPatch" -}} +- command: + - "/bin/bash" + - "-ec" + - | + timeout 600 bash -c ' + STATE=" " + until [ "$STATE" = "Running" ]; + do + echo "Waiting for redis enterprise cluster to be running"; STATE=$(kubectl get --namespace="{{ .Release.Namespace }}" rec/redis-enterprise -o jsonpath='{.status.state}') ; sleep 5; + done + ' + + name: wait-for-cr-patch-created + image: {{ .Values.deployerHelm.image }} +{{- end -}} diff --git a/chart/redis-operator/templates/application.yaml b/chart/redis-operator/templates/application.yaml index bc74fa7..85cf8fa 100644 --- a/chart/redis-operator/templates/application.yaml +++ b/chart/redis-operator/templates/application.yaml @@ -26,8 +26,6 @@ spec: matchLabels: app.kubernetes.io/name: "{{ .Release.Name }}" componentKinds: - - group: apps/v1 - kind: Deployment - group: v1 kind: ConfigMap - group: v1 @@ -40,8 +38,7 @@ spec: kind: Service - group: apps/v1 kind: StatefulSet + - group: v1 + kind: PersistentVolume - group: v1 kind: PersistentVolumeClaim - - group: app.redislabs.com/v1 - kind: RedisEnterpriseCluster - \ No newline at end of file diff --git a/chart/redis-operator/templates/job/cr-patch.yaml b/chart/redis-operator/templates/job/cr-patch.yaml new file mode 100644 index 0000000..f908c16 --- /dev/null +++ b/chart/redis-operator/templates/job/cr-patch.yaml @@ -0,0 +1,57 @@ +apiVersion: batch/v1 +kind: Job +metadata: + annotations: + name: {{ template "redis_operator.CRPatchJob" . }} + labels: + app.kubernetes.io/name: "{{ .Release.Name }}" + app.kubernetes.io/component: cr-patch-job +spec: + ttlSecondsAfterFinished: 300 + backoffLimit: 0 + completions: 1 + parallelism: 1 + template: + spec: + initContainers: + {{- include "initContainerWaitForCRPatch" . | nindent 6 }} + containers: + - command: + - "/bin/bash" + - "-ec" + - | + app_uid="$(kubectl get "applications.app.k8s.io/{{ .Release.Name }}" \ + --namespace="{{ .Release.Namespace }}" \ + --output=jsonpath='{.metadata.uid}')" + app_api_version="$(kubectl get "applications.app.k8s.io/{{ .Release.Name }}" \ + --namespace="{{ .Release.Namespace }}" \ + --output=jsonpath='{.apiVersion}')" + patch="$(echo '{}' \ + | jq '{ + "metadata": { + "ownerReferences": [ + { + "apiVersion": $app_api_version, + "kind": "Application", + "name": $name, + "uid": $app_uid, + "blockOwnerDeletion": true + } + ], + "labels": { + "app.kubernetes.io/name": $name, + "app.kubernetes.io/namespace": $namespace + } + } + }' \ + --arg name {{ .Release.Name }} \ + --arg namespace {{ .Release.Namespace }} \ + --arg app_uid $app_uid \ + --arg app_api_version $app_api_version)" + kubectl patch --namespace="{{ .Release.Namespace }}" RedisEnterpriseCluster/redis-enterprise --patch "$patch" --type merge --output json + image: {{ .Values.deployerHelm.image }} + imagePullPolicy: Always + name: cr-patch + dnsPolicy: ClusterFirst + restartPolicy: Never + serviceAccountName: {{ .Values.CRJobServiceAccount }} diff --git a/deployer/Dockerfile b/deployer/Dockerfile index 1f5a861..cd39359 100644 --- a/deployer/Dockerfile +++ b/deployer/Dockerfile @@ -34,7 +34,11 @@ FROM gcr.io/cloud-marketplace-tools/k8s/deployer_helm:$MARKETPLACE_TOOLS_TAG ARG CHART_NAME +ENV WAIT_FOR_READY_TIMEOUT 3600 +ENV TESTER_TIMEOUT 3600 + COPY --from=build /tmp/$CHART_NAME.tar.gz /data/chart/ COPY --from=build /tmp/test/$CHART_NAME.tar.gz /data-test/chart/ COPY --from=build /tmp/apptest/schema.yaml /data-test/ COPY --from=build /tmp/schema.yaml /data/ + diff --git a/dev-test-create.sh b/dev-test-create.sh new file mode 100755 index 0000000..0654279 --- /dev/null +++ b/dev-test-create.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +gcloud container clusters create redis --zone us-central1-c --machine-type n2-standard-8 +kubectl create ns redis +make crd/install diff --git a/dev-test-delete.sh b/dev-test-delete.sh new file mode 100755 index 0000000..4484264 --- /dev/null +++ b/dev-test-delete.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +gcloud container clusters delete redis --zone us-central1-c diff --git a/dev-test-install.sh b/dev-test-install.sh new file mode 100755 index 0000000..c7d0584 --- /dev/null +++ b/dev-test-install.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +mpdev install --deployer=gcr.io/proven-reality-226706/redislabs/deployer:6.002012 --parameters='{"name": "redis-enterprise-operator", "namespace": "redis", "operator.nodeCpu": 5000, "operator.nodeMem": 16, "reportingSecret": "gs://cloud-marketplace-tools/reporting_secrets/fake_reporting_secret.yaml"}' | tee install.log diff --git a/dev-test-verify.sh b/dev-test-verify.sh new file mode 100755 index 0000000..51c8a33 --- /dev/null +++ b/dev-test-verify.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +mpdev verify --deployer=gcr.io/proven-reality-226706/redislabs/deployer:6.002012 --wait_timeout=3600 | tee verify.log diff --git a/schema.yaml b/schema.yaml index ff21b43..ac6423d 100644 --- a/schema.yaml +++ b/schema.yaml @@ -28,6 +28,15 @@ x-google-marketplace: requests: cpu: 4000m memory: 15Gi + deployerServiceAccount: + description: Service account for Deployer + roles: + - type: ClusterRole + rulesType: CUSTOM + rules: + - apiGroups: [""] + resources: ["persistentvolumes", "clusterroles", "clusterrolebindings"] + verbs: ["get", "watch", "list", "update", "patch", "create", "delete"] properties: name: type: string @@ -83,7 +92,7 @@ properties: verbs: ["create", "delete", "get" , "update", "list", "watch"] - apiGroups: [""] resources: ["persistentvolumeclaims"] - verbs: ["create", "delete", "get" , "update"] + verbs: ["create", "delete", "get" , "update", "list"] # needed rbac rules for services controller - apiGroups: [""] resources: ["pods"] @@ -115,7 +124,7 @@ properties: - type: ClusterRole rulesType: CUSTOM rules: - - apiGroups: ["app.redislabs.com", "apiextensions.k8s.io"] + - apiGroups: ["app.k8s.io", "app.redislabs.com", "apiextensions.k8s.io"] resources: ["*"] verbs: ["*"] operator.replicas: From 27a84fba05d710e4bfee8a217acdccea066e4e58 Mon Sep 17 00:00:00 2001 From: Sambasiva Andaluri Date: Fri, 8 Oct 2021 05:07:11 -0400 Subject: [PATCH 085/120] fix storage class --- schema.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/schema.yaml b/schema.yaml index ac6423d..c317fb8 100644 --- a/schema.yaml +++ b/schema.yaml @@ -157,6 +157,10 @@ properties: type: string description: Storage class default: standard + x-google-marketplace: + type: STORAGE_CLASS + storageClass: + type: SSD operator.nodeCpu: title: Node CPU [millis] type: integer From 1e391a246879db8667153151d995e5902ee98ea0 Mon Sep 17 00:00:00 2001 From: Nick Terner Date: Mon, 31 Jan 2022 11:02:29 +0200 Subject: [PATCH 086/120] CI integration - add dev/install make target, replacing dev-test-install.sh script - missing testapp role+binding for dev/CI deployments - admission service was not cleaned up --- Makefile | 14 +++++++++++--- .../templates/service/admission-service.yaml | 3 +++ testapp-role.yaml | 8 ++++++++ testapp-rolebinding.yaml | 13 +++++++++++++ 4 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 testapp-role.yaml create mode 100644 testapp-rolebinding.yaml diff --git a/Makefile b/Makefile index 9c1412d..97b1cc7 100644 --- a/Makefile +++ b/Makefile @@ -12,8 +12,10 @@ include ../var.Makefile #REGISTRY ?= marketplace.gcr.io/google/redis-enterprise-operator # Artifact repo #REGISTRY := us-central1-docker.pkg.dev/proven-reality-226706/redis-market-place -# Container repo -REGISTRY := gcr.io/proven-reality-226706/redislabs +# the repo the publish copies from? +#REGISTRY := gcr.io/proven-reality-226706/redislabs +# CI registry +REGISTRY ?= gcr.io/redislabs-k8s-dev-238506/gkemp-redis-ci $(info ---- REGISTRY = $(REGISTRY)) @@ -36,7 +38,7 @@ $(info ---- DEPLOYER_TAG = $(DEPLOYER_TAG)) # Tag the deployer image with modified version. APP_DEPLOYER_IMAGE := $(REGISTRY)/deployer:$(DEPLOYER_TAG) -NAME ?= redis-enterprise-operator-1 +NAME ?= redis-enterprise-operator-ci NAMESPACE ?= redis APP_PARAMETERS ?= { \ @@ -46,6 +48,12 @@ APP_PARAMETERS ?= { \ TESTER_IMAGE ?= $(REGISTRY)/tester:$(OPERATOR_TAG) +.PHONY: dev/install +dev/install: crd/install | .build/app/dev + .build/app/dev install \ + --deployer='$(APP_DEPLOYER_IMAGE)' --parameters='{"name": "redis-enterprise-operator", "namespace": "$(NAMESPACE)", "operator.nodeCpu": 5000, "operator.nodeMem": 16, "reportingSecret": "gs://cloud-marketplace-tools/reporting_secrets/fake_reporting_secret.yaml"}' | tee install.log + + app/build:: .build/redis-enterprise-operator/deployer \ .build/redis-enterprise-operator/primary \ .build/redis-enterprise-operator/usage-meter \ diff --git a/chart/redis-operator/templates/service/admission-service.yaml b/chart/redis-operator/templates/service/admission-service.yaml index 200987f..6be2c59 100644 --- a/chart/redis-operator/templates/service/admission-service.yaml +++ b/chart/redis-operator/templates/service/admission-service.yaml @@ -1,6 +1,9 @@ apiVersion: v1 kind: Service metadata: + labels: + app: redis-enterprise + app.kubernetes.io/name: redis-enterprise-operator name: admission spec: ports: diff --git a/testapp-role.yaml b/testapp-role.yaml new file mode 100644 index 0000000..409f386 --- /dev/null +++ b/testapp-role.yaml @@ -0,0 +1,8 @@ +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: testapp +rules: + - apiGroups: ["app.k8s.io"] + resources: ["applications"] + verbs: ["*"] diff --git a/testapp-rolebinding.yaml b/testapp-rolebinding.yaml new file mode 100644 index 0000000..fc55663 --- /dev/null +++ b/testapp-rolebinding.yaml @@ -0,0 +1,13 @@ +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: testapp +subjects: +- kind: ServiceAccount + name: default +- kind: ServiceAccount + name: redis-enterprise-operator +roleRef: + kind: Role + name: testapp + apiGroup: rbac.authorization.k8s.io From 1c23291a5b4284a6a10cc4b172d141b4ad5afdbf Mon Sep 17 00:00:00 2001 From: alon-zada <84660065+alon-zada@users.noreply.github.com> Date: Mon, 14 Feb 2022 17:45:03 +0200 Subject: [PATCH 087/120] add support to control the operator repo from Docker Hub to pull from. (#23) --- Makefile | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 97b1cc7..a65591d 100644 --- a/Makefile +++ b/Makefile @@ -28,6 +28,10 @@ $(info ---- REDIS_TAG = $(REDIS_TAG)) OPERATOR_TAG ?= 6.0.20-12 $(info ---- OPERATOR_TAG = $(OPERATOR_TAG)) +# The repo to pull the operator image from Docker Hub registry. +OPERATOR_REPO ?= operator +$(info ---- OPERATOR_REPO = $(OPERATOR_REPO)) + # Deployer tag is used for displaying versions in partner portal. # This version only support major.minor so the Redis version major.minor.patch # is converted into more readable form of major.2 digit zero padded minor + patch @@ -94,8 +98,8 @@ app/build:: .build/redis-enterprise-operator/deployer \ .build/var/DEPLOYER_TAG \ | .build/redis-enterprise-operator $(call print_target, $@) - docker pull redislabs/operator:$(OPERATOR_TAG) - docker tag redislabs/operator:$(OPERATOR_TAG) "$(REGISTRY):$(OPERATOR_TAG)" + docker pull redislabs/$(OPERATOR_REPO):$(OPERATOR_TAG) + docker tag redislabs/$(OPERATOR_REPO):$(OPERATOR_TAG) "$(REGISTRY):$(OPERATOR_TAG)" docker push "$(REGISTRY):$(OPERATOR_TAG)" @touch "$@" From fca570cddab2829467b65974bc5029de7e46bf4d Mon Sep 17 00:00:00 2001 From: Nick Terner Date: Tue, 1 Mar 2022 16:46:47 +0200 Subject: [PATCH 088/120] change deletion flow (#24) rely on the user to delete the REC resource before deleting the app Also: - CommonMark is partially supported for the rich description text - the Application CR instance now owns the Operator deployment, as is customary in the marketplace. And the admission service too --- Makefile | 14 ++++++++------ chart/redis-operator/templates/_helpers.tpl | 12 ++++++------ chart/redis-operator/templates/application.yaml | 7 +++++++ .../job/{cr-patch.yaml => deploy-patch.yaml} | 13 +++++++------ schema.yaml | 15 ++++++++++++--- testapp-role.yaml | 6 ++++++ 6 files changed, 46 insertions(+), 21 deletions(-) rename chart/redis-operator/templates/job/{cr-patch.yaml => deploy-patch.yaml} (76%) diff --git a/Makefile b/Makefile index a65591d..5a2ca8b 100644 --- a/Makefile +++ b/Makefile @@ -3,11 +3,6 @@ include ../app.Makefile # crd.Makefile provides targets to install Application CRD. include ../crd.Makefile -# gcloud.Makefile provides default values for REGISTRY and NAMESPACE derived from local -# gcloud and kubectl environments. -include ../gcloud.Makefile -include ../var.Makefile - # Production repo #REGISTRY ?= marketplace.gcr.io/google/redis-enterprise-operator # Artifact repo @@ -16,6 +11,10 @@ include ../var.Makefile #REGISTRY := gcr.io/proven-reality-226706/redislabs # CI registry REGISTRY ?= gcr.io/redislabs-k8s-dev-238506/gkemp-redis-ci +# gcloud.Makefile provides default values for REGISTRY and NAMESPACE derived from local +# gcloud and kubectl environments. +include ../gcloud.Makefile +include ../var.Makefile $(info ---- REGISTRY = $(REGISTRY)) @@ -36,7 +35,9 @@ $(info ---- OPERATOR_REPO = $(OPERATOR_REPO)) # This version only support major.minor so the Redis version major.minor.patch # is converted into more readable form of major.2 digit zero padded minor + patch # without the hyphen -DEPLOYER_TAG ?= 6.002012 +# This can also have a different patch number from the OPERATOR_TAG to indicate +# a marketplace-only change +DEPLOYER_TAG ?= 6.002053 $(info ---- DEPLOYER_TAG = $(DEPLOYER_TAG)) # Tag the deployer image with modified version. @@ -70,6 +71,7 @@ app/build:: .build/redis-enterprise-operator/deployer \ .build/redis-enterprise-operator/deployer: deployer/* \ chart/**/* \ schema.yaml \ + .build/var/DEPLOYER_TAG \ .build/var/APP_DEPLOYER_IMAGE \ .build/var/MARKETPLACE_TOOLS_TAG \ .build/var/REGISTRY \ diff --git a/chart/redis-operator/templates/_helpers.tpl b/chart/redis-operator/templates/_helpers.tpl index bf30208..16d0c95 100644 --- a/chart/redis-operator/templates/_helpers.tpl +++ b/chart/redis-operator/templates/_helpers.tpl @@ -6,8 +6,8 @@ {{- printf "%s-crd-job" .Release.Name | trunc 63 -}} {{- end -}} -{{- define "redis_operator.CRPatchJob" -}} -{{- printf "%s-cr-patch-job" .Release.Name | trunc 63 -}} +{{- define "redis_operator.DeployPatchJob" -}} +{{- printf "%s-deploy-patch-job" .Release.Name | trunc 63 -}} {{- end -}} {{- define "redis_operator.CRsConfigMap" -}} @@ -42,16 +42,16 @@ image: {{ .Values.deployerHelm.image }} {{- end -}} -{{- define "initContainerWaitForCRPatch" -}} +{{- define "initContainerWaitForOperator" -}} - command: - "/bin/bash" - "-ec" - | timeout 600 bash -c ' - STATE=" " - until [ "$STATE" = "Running" ]; + STATE="PendingCreation" + while [ "$STATE" = "PendingCreation" ]; do - echo "Waiting for redis enterprise cluster to be running"; STATE=$(kubectl get --namespace="{{ .Release.Namespace }}" rec/redis-enterprise -o jsonpath='{.status.state}') ; sleep 5; + echo "Waiting for redis enterprise operator to be active"; STATE=$(kubectl get --namespace="{{ .Release.Namespace }}" rec/redis-enterprise -o jsonpath='{.status.state}') ; sleep 5; done ' diff --git a/chart/redis-operator/templates/application.yaml b/chart/redis-operator/templates/application.yaml index 85cf8fa..63c5af1 100644 --- a/chart/redis-operator/templates/application.yaml +++ b/chart/redis-operator/templates/application.yaml @@ -22,6 +22,11 @@ spec: url: https://support.redislabs.com notes: |- See more details and manual installation instructions here https://github.com/RedisLabs/gkemarketplace/README.md + ## ⚠️ BEFORE DELETING + #### Before deleting the Application, make sure to delete any Custom Resources from the K8s cluster: + ##### 1. first any REDB resources + ##### 2. followed by deleting the `rec` (instance of REC) + ##### 3. then the Application can be safely Deleted selector: matchLabels: app.kubernetes.io/name: "{{ .Release.Name }}" @@ -36,6 +41,8 @@ spec: kind: Job - group: v1 kind: Service + - group: apps/v1 + kind: Deployment - group: apps/v1 kind: StatefulSet - group: v1 diff --git a/chart/redis-operator/templates/job/cr-patch.yaml b/chart/redis-operator/templates/job/deploy-patch.yaml similarity index 76% rename from chart/redis-operator/templates/job/cr-patch.yaml rename to chart/redis-operator/templates/job/deploy-patch.yaml index f908c16..fe1aafa 100644 --- a/chart/redis-operator/templates/job/cr-patch.yaml +++ b/chart/redis-operator/templates/job/deploy-patch.yaml @@ -2,10 +2,10 @@ apiVersion: batch/v1 kind: Job metadata: annotations: - name: {{ template "redis_operator.CRPatchJob" . }} + name: {{ template "redis_operator.DeployPatchJob" . }} labels: app.kubernetes.io/name: "{{ .Release.Name }}" - app.kubernetes.io/component: cr-patch-job + app.kubernetes.io/component: deploy-patch-job spec: ttlSecondsAfterFinished: 300 backoffLimit: 0 @@ -14,7 +14,7 @@ spec: template: spec: initContainers: - {{- include "initContainerWaitForCRPatch" . | nindent 6 }} + {{- include "initContainerWaitForOperator" . | nindent 6 }} containers: - command: - "/bin/bash" @@ -48,10 +48,11 @@ spec: --arg namespace {{ .Release.Namespace }} \ --arg app_uid $app_uid \ --arg app_api_version $app_api_version)" - kubectl patch --namespace="{{ .Release.Namespace }}" RedisEnterpriseCluster/redis-enterprise --patch "$patch" --type merge --output json + kubectl patch --namespace="{{ .Release.Namespace }}" deploy/redis-enterprise-operator --patch "$patch" --type merge --output json + kubectl patch --namespace="{{ .Release.Namespace }}" service/admission --patch "$patch" --type merge --output json image: {{ .Values.deployerHelm.image }} imagePullPolicy: Always - name: cr-patch + name: deploy-patch dnsPolicy: ClusterFirst restartPolicy: Never - serviceAccountName: {{ .Values.CRJobServiceAccount }} + serviceAccountName: {{ .Values.DeployJobServiceAccount }} diff --git a/schema.yaml b/schema.yaml index c317fb8..e9ea76a 100644 --- a/schema.yaml +++ b/schema.yaml @@ -113,20 +113,29 @@ properties: - apiGroups: ["extensions"] resources: ["ingresses"] verbs: ["*"] - CRJobServiceAccount: + DeployJobServiceAccount: type: string title: Service account for custom resources x-google-marketplace: type: SERVICE_ACCOUNT serviceAccount: - description: Service account for custom resources + description: Service account for ownerReference chain roles: - type: ClusterRole rulesType: CUSTOM rules: - - apiGroups: ["app.k8s.io", "app.redislabs.com", "apiextensions.k8s.io"] + - apiGroups: ["app.k8s.io", "apiextensions.k8s.io"] resources: ["*"] verbs: ["*"] + - type: Role + rulesType: CUSTOM + rules: + - apiGroups: ["apps"] + resources: ["deployments"] + verbs: ["*"] + - apiGroups: [""] + resources: ["services"] + verbs: ["*"] operator.replicas: type: integer title: Number of Cluster Nodes diff --git a/testapp-role.yaml b/testapp-role.yaml index 409f386..df11f5f 100644 --- a/testapp-role.yaml +++ b/testapp-role.yaml @@ -3,6 +3,12 @@ apiVersion: rbac.authorization.k8s.io/v1 metadata: name: testapp rules: + - apiGroups: ["apps"] + resources: ["deployments"] + verbs: ["*"] - apiGroups: ["app.k8s.io"] resources: ["applications"] verbs: ["*"] + - apiGroups: [""] + resources: ["services"] + verbs: ["*"] From 845180f34adbc9b18b60d54a52fb8f02b7f473ec Mon Sep 17 00:00:00 2001 From: Nick Terner Date: Tue, 1 Mar 2022 14:01:47 +0200 Subject: [PATCH 089/120] update for compatibility with operator 6.2.10-4 https://github.com/RedisLabs/redis-enterprise-k8s-docs/compare/v6.0.20-12...6.2.10-4 in fact, though it wasn't properly updated from 6.0.12: https://github.com/RedisLabs/redis-enterprise-k8s-docs/compare/v6.0.12-5...6.2.10-4 also have the admission webhook deployed automatically --- Makefile | 6 +- README.md | 4 +- chart/redis-operator/Chart.yaml | 2 +- chart/redis-operator/files/crd/rec_crd.yaml | 15083 ++++++++++++++-- chart/redis-operator/files/crd/redb_crd.yaml | 1110 +- chart/redis-operator/templates/_helpers.tpl | 1 + .../redis-operator/templates/application.yaml | 3 +- .../templates/configmap/cr.yaml | 2 +- .../templates/deployment/operator.yaml | 62 +- .../templates/job/cr-create.yaml | 29 + schema.yaml | 47 +- 11 files changed, 14234 insertions(+), 2115 deletions(-) diff --git a/Makefile b/Makefile index 5a2ca8b..b7a3708 100644 --- a/Makefile +++ b/Makefile @@ -21,10 +21,10 @@ $(info ---- REGISTRY = $(REGISTRY)) CHART_NAME := redis-operator $(info ---- CHART_NAME = $(CHART_NAME)) -REDIS_TAG ?= 6.0.20-97 +REDIS_TAG ?= 6.2.10-83 $(info ---- REDIS_TAG = $(REDIS_TAG)) -OPERATOR_TAG ?= 6.0.20-12 +OPERATOR_TAG ?= 6.2.10-3 $(info ---- OPERATOR_TAG = $(OPERATOR_TAG)) # The repo to pull the operator image from Docker Hub registry. @@ -37,7 +37,7 @@ $(info ---- OPERATOR_REPO = $(OPERATOR_REPO)) # without the hyphen # This can also have a different patch number from the OPERATOR_TAG to indicate # a marketplace-only change -DEPLOYER_TAG ?= 6.002053 +DEPLOYER_TAG ?= 6.021001 $(info ---- DEPLOYER_TAG = $(DEPLOYER_TAG)) # Tag the deployer image with modified version. diff --git a/README.md b/README.md index 0419e7b..c490ee7 100644 --- a/README.md +++ b/README.md @@ -88,8 +88,8 @@ Redis version tags are in the format Major.Minor.Patch-Sub but GKE Marketplace r ```shell export APP_INSTANCE_NAME=redis-enterprise-operator export NAMESPACE=redis -export TAG=6.0.20-12 -export DEPLOYER_TAG=6.002012 +export TAG=6.2.10-3 +export DEPLOYER_TAG=6.021001 export REPO=gcr.io/cloud-marketplace/redislabs-public/redis-enterprise ``` diff --git a/chart/redis-operator/Chart.yaml b/chart/redis-operator/Chart.yaml index 473d7f2..797502c 100644 --- a/chart/redis-operator/Chart.yaml +++ b/chart/redis-operator/Chart.yaml @@ -1,3 +1,3 @@ apiVersion: "v2" name: redis-operator -version: "1.20" +version: "1.21" diff --git a/chart/redis-operator/files/crd/rec_crd.yaml b/chart/redis-operator/files/crd/rec_crd.yaml index 92de32d..8815913 100644 --- a/chart/redis-operator/files/crd/rec_crd.yaml +++ b/chart/redis-operator/files/crd/rec_crd.yaml @@ -1,33 +1,8 @@ -apiVersion: apiextensions.k8s.io/v1beta1 +apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: redisenterpriseclusters.app.redislabs.com spec: - additionalPrinterColumns: - - JSONPath: .spec.nodes - name: Nodes - type: string - - JSONPath: .spec.redisEnterpriseImageSpec.versionTag - name: Version - type: string - - JSONPath: .status.state - name: State - type: string - - JSONPath: .status.specStatus - name: Spec Status - type: string - - JSONPath: .status.licenseStatus.licenseState - name: License State - type: string - - JSONPath: .status.licenseStatus.shardsLimit - name: Shards Limit - type: string - - JSONPath: .status.licenseStatus.expirationDate - name: License Expiration Date - type: string - - name: Age - type: date - JSONPath: .metadata.creationTimestamp group: app.redislabs.com names: kind: RedisEnterpriseCluster @@ -37,1698 +12,13668 @@ spec: shortNames: - rec scope: Namespaced - subresources: - status: {} - version: v1 + preserveUnknownFields: false versions: - name: v1 served: true storage: false - - name: v1alpha1 - served: true - storage: true - validation: - openAPIV3Schema: - description: RedisEnterpriseCluster is the Schema for the redisenterpriseclusters - API - properties: - apiVersion: - type: string - kind: - type: string - metadata: - type: object - spec: - description: RedisEnterpriseClusterSpec defines the desired state of RedisEnterpriseCluster - properties: - activeActive: - description: Specification for ActiveActive setup - properties: - apiIngressUrl: - description: RS API URL - type: string - dbIngressSuffix: - description: DB ENDPOINT SUFFIX - will be used to set the db host. - ingress Creates a host name so it - should be unique if more than one db is created on the cluster - with the same name - type: string - ingressAnnotations: - additionalProperties: - type: string - description: Used for ingress controllers such as ha-proxy or nginx - in GKE - type: object - method: - description: Used to distinguish between different platforms implementation - enum: - - openShiftRoute - - ingress - type: string - required: - - apiIngressUrl - - dbIngressSuffix - - method - type: object - antiAffinityAdditionalTopologyKeys: - description: Additional antiAffinity terms in order to support installation - on different zones/vcenters - items: + subresources: + status: {} + additionalPrinterColumns: + - jsonPath: .spec.nodes + name: Nodes + type: string + - jsonPath: .spec.redisEnterpriseImageSpec.versionTag + name: Version + type: string + - jsonPath: .status.state + name: State + type: string + - jsonPath: .status.specStatus + name: Spec Status + type: string + - jsonPath: .status.licenseStatus.licenseState + name: License State + type: string + - jsonPath: .status.licenseStatus.shardsLimit + name: Shards Limit + type: string + - jsonPath: .status.licenseStatus.expirationDate + name: License Expiration Date + type: string + - name: Age + type: date + jsonPath: .metadata.creationTimestamp + schema: + openAPIV3Schema: + description: RedisEnterpriseCluster is the Schema for the redisenterpriseclusters + API + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + status: + type: object + properties: + specStatus: type: string - type: array - bootstrapperImageSpec: - description: Specification for Bootstrapper container image - properties: - imagePullPolicy: - description: PullPolicy describes a policy for if/when to pull a - container image - type: string - repository: - description: Repository - type: string - versionTag: - type: string - type: object - bootstrapperResources: - description: Compute resource requirements for bootstrapper containers - 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-compute-resources-container/' - 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-compute-resources-container/' - type: object - type: object - clusterCredentialSecretName: - description: Secret Name/Path to use for Cluster Credentials. If left - blank, will use cluster name - type: string - clusterCredentialSecretRole: - description: Used only if ClusterCredentialSecretType is vault, to define - vault role to be used. If blank, defaults to "redis-enterprise-operator" - type: string - clusterCredentialSecretType: - description: Type of Secret to use for ClusterCredential, Vault, Kuberetes,... - If left blank, will default ot kubernetes secrets - enum: - - vault - - kubernetes - type: string - clusterRecovery: - description: ClusterRecovery initiates cluster recovery when set to - true. Note that this field is cleared automatically after the cluster - is recovered - type: boolean - createServiceAccount: - description: Whether to create service account - type: boolean - enforceIPv4: - description: Sets ENFORCE_IPV4 environment variable - type: boolean - extraLabels: - additionalProperties: + state: type: string - description: Labels that the user defines for their convenience - type: object - hostAliases: - items: - description: HostAlias holds the mapping between IP and hostnames - that will be injected as an entry in the pod's hosts file. - properties: - hostnames: - description: Hostnames for the above IP address. - items: + modules: + type: array + items: + type: object + properties: + name: type: string - type: array - ip: - description: IP address of the host file entry. + displayName: + type: string + versions: + type: array + items: + type: string + ocspStatus: + description: An API object that represents the cluster's OCSP status + properties: + certStatus: + description: Indicates the proxy certificate status - GOOD/REVOKED/UNKNOWN. + type: string + nextUpdate: + description: The time at or before which newer information will + be available about the status of the certificate (if available) + type: string + producedAt: + description: The time at which the OCSP responder signed this + response. + type: string + responderUrl: + description: The OCSP responder url from which this status came + from. + type: string + revocationTime: + description: The time at which the certificate was revoked or + placed on hold. + type: string + thisUpdate: + description: The most recent time at which the status being indicated + is known by the responder to have been correct. type: string type: object - type: array - license: - description: Redis Enterprise License - type: string - nodeSelector: - additionalProperties: - type: string - description: Selector for nodes that could fit Redis Enterprise pod - type: object - nodes: - description: Number of Redis Enterprise nodes (pods) - format: int32 - type: integer - persistentSpec: - description: Specification for Redis Enterprise Cluster persistence - properties: - enabled: - description: Whether to add persistent volume to Redis Enterprise - pods - type: boolean - storageClassName: - description: Storage class for persistent volume in Redis Enterprise - pods Leave empty to use the default - type: string - volumeSize: - 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 - type: object - podAnnotations: - additionalProperties: - type: string - description: pod annotations - type: object - podAntiAffinity: - description: 'Override for the default anti-affinity rules of the Redis - Enterprise pods. - More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#an-example-of-a-pod-that-uses-pod-affinity' - properties: - preferredDuringSchedulingIgnoredDuringExecution: - items: - properties: - podAffinityTerm: - properties: - labelSelector: - properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - type: object - type: object - namespaces: - items: - type: string - type: array - topologyKey: - type: string - required: - - topologyKey - type: object - weight: - format: int32 - type: integer - required: - - podAffinityTerm - - weight - type: object - type: array - requiredDuringSchedulingIgnoredDuringExecution: - items: - properties: - labelSelector: - properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - type: object - type: object - namespaces: - items: - type: string - type: array - topologyKey: - type: string - required: - - topologyKey - type: object - type: array - type: object - podSecurityPolicyName: - description: Name of pod security policy to use on pods See https://kubernetes.io/docs/concepts/policy/pod-security-policy/ - type: string - podTolerations: - description: 'Tolerations that are added to all managed pods. More - information: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/' - items: + licenseStatus: + type: object properties: - effect: + licenseState: type: string - key: + activationDate: type: string - operator: + expirationDate: type: string - tolerationSeconds: - format: int64 + shardsLimit: type: integer - value: + bundledDatabaseVersions: + description: Versions of open source databases bundled by Redis Enterprise + Software - please note that in order to use a specific version it + should be supported by the ‘upgradePolicy’ - ‘major’ or ‘latest’ according + to the desired version (major/minor) + items: + properties: + dbType: + type: string + version: + type: string + required: + - dbType + - version + type: object + type: array + spec: + description: RedisEnterpriseClusterSpec defines the desired state of RedisEnterpriseCluster + properties: + activeActive: + description: Specification for ActiveActive setup + properties: + apiIngressUrl: + description: RS API URL + type: string + dbIngressSuffix: + description: DB ENDPOINT SUFFIX - will be used to set the db host. + ingress Creates a host name so it + should be unique if more than one db is created on the cluster + with the same name type: string + ingressAnnotations: + additionalProperties: + type: string + description: Used for ingress controllers such as ha-proxy or nginx + in GKE + type: object + method: + description: Used to distinguish between different platforms implementation + enum: + - openShiftRoute + - ingress + type: string + required: + - apiIngressUrl + - dbIngressSuffix + - method type: object - type: array - priorityClassName: - description: Adds the priority class to pods managed by the operator - type: string - pullSecrets: - description: 'PullSecrets is an optional list of references to secrets - in the same namespace to use for pulling any of the images. If specified, - these secrets will be passed to individual puller implementations - for them to use. More info: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/' - items: + antiAffinityAdditionalTopologyKeys: + description: Additional antiAffinity terms in order to support installation + on different zones/vcenters + items: + type: string + type: array + bootstrapperImageSpec: + description: Specification for Bootstrapper container image properties: - name: - description: 'Secret name' + imagePullPolicy: + description: PullPolicy describes a policy for if/when to pull a + container image + type: string + repository: + description: Repository + type: string + versionTag: type: string type: object - type: array - rackAwarenessNodeLabel: - description: Node label that specifies rack ID - if specified, will - create rack aware cluster. Rack awareness requires node label must - exist on all nodes. Additionally, operator needs a special cluster - role with permission to list nodes. - type: string - redisEnterpriseImageSpec: - description: Specification for Redis Enterprise container image - properties: - imagePullPolicy: - description: PullPolicy describes a policy for if/when to pull a - container image - type: string - repository: - description: Repository - type: string - versionTag: - type: string - type: object - redisEnterpriseNodeResources: - description: Compute resource requirements for Redis Enterprise containers - 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-compute-resources-container/' - 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-compute-resources-container/' - type: object - type: object - redisEnterpriseServicesRiggerImageSpec: - description: Specification for Services Rigger container image - properties: - imagePullPolicy: - description: PullPolicy describes a policy for if/when to pull a - container image - type: string - repository: - description: Repository - type: string - versionTag: - type: string - type: object - redisEnterpriseServicesRiggerResources: - description: Compute resource requirements for Services Rigger pod - 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-compute-resources-container/' - 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-compute-resources-container/' - type: object - type: object - redisEnterpriseVolumeMounts: - description: 'additional volume mounts within the redis enterprise containers. - More info: https://kubernetes.io/docs/concepts/storage/volumes/' - items: + bootstrapperResources: + description: Compute resource requirements for bootstrapper containers + 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-compute-resources-container/' + 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-compute-resources-container/' + type: object + type: object + clusterCredentialSecretName: + description: Secret Name/Path to use for Cluster Credentials. If left + blank, will use cluster name + type: string + clusterCredentialSecretRole: + description: Used only if ClusterCredentialSecretType is vault, to define + vault role to be used. If blank, defaults to "redis-enterprise-operator" + type: string + clusterCredentialSecretType: + description: Type of Secret to use for ClusterCredential, Vault, Kuberetes,... + If left blank, will default ot kubernetes secrets + enum: + - vault + - kubernetes + type: string + clusterRecovery: + description: ClusterRecovery initiates cluster recovery when set to + true. Note that this field is cleared automatically after the cluster + is recovered + type: boolean + createServiceAccount: + description: Whether to create service account + type: boolean + dataInternodeEncryption: + description: Internode encryption (INE) cluster wide policy. An optional boolean setting. + Specifies if INE should be on/off for new created REDBs. + May be overridden for specific REDB via similar setting, + please view the similar setting for REDB for more info. + type: boolean + encryptPkeys: + description: 'Private key encryption - in order to enable, first need + to mount ${ephemeralconfdir}/secrets/pem/passphrase and add the passphrase + and then set fields value to ''true'' Possible values: true/false' + type: boolean + certificates: + description: RS Cluster Certificates. + Used to modify the certificates used by the cluster. + See the "RSClusterCertificates" struct described above to see the supported certificates. properties: - mountPath: + apiCertificateSecretName: + description: Secret Name/Path to use for Cluster's API Certificate. + If left blank, will use certificate provided by the cluster. type: string - mountPropagation: + cmCertificateSecretName: + description: Secret Name/Path to use for Cluster's CM Certificate. + If left blank, will use certificate provided by the cluster. type: string - name: + metricsExporterCertificateSecretName: + description: Secret Name/Path to use for Cluster's Metrics Exporter Certificate. + If left blank, will use certificate provided by the cluster. type: string - readOnly: - type: boolean - subPath: + proxyCertificateSecretName: + description: Secret Name/Path to use for Cluster's Proxy Certificate. + If left blank, will use certificate provided by the cluster. type: string - subPathExpr: + syncerCertificateSecretName: + description: Secret Name/Path to use for Cluster's Syncer Certificate. + If left blank, will use certificate provided by the cluster. type: string - required: - - mountPath - - name type: object - type: array - serviceAccountName: - description: Name of the service account to use - type: string - redisEnterpriseServicesConfiguration: - description: Cluster optional services settings - properties: - mdnsServer: + enforceIPv4: + description: Sets ENFORCE_IPV4 environment variable + type: boolean + extraLabels: + additionalProperties: + type: string + description: Labels that the user defines for their convenience + type: object + hostAliases: + items: + description: HostAlias holds the mapping between IP and hostnames + that will be injected as an entry in the pod's hosts file. properties: - operatingMode: - enum: - - enabled - - disabled + hostnames: + description: Hostnames for the above IP address. + items: + type: string + type: array + ip: + description: IP address of the host file entry. type: string - required: - - operatingMode type: object - type: object - servicesRiggerSpec: - description: Specification for service rigger - properties: - databaseServiceType: - description: Service types for access to databases. should be a - comma separated list. The possible values are cluster_ip, headless - and load_balancer. + type: array + license: + description: Redis Enterprise License + type: string + licenseSecretName: + description: K8s secret or Vault Secret Name/Path to use for Cluster + License. When left blank, the license is read from the "license" field. + Note that you can't specify non-empty values in both "license" and + "licenseSecretName", only one of these fields can be used to pass + the license string. The license needs to be stored under the key "license". + type: string + nodeSelector: + additionalProperties: type: string - extraEnvVars: - items: - description: 'EnvVar represents an environment variable present - in a Container. - More info: https://kubernetes.io/docs/tasks/inject-data-application/environment-variable-expose-pod-information/' - properties: - name: - description: Name of the environment variable. - type: string - value: - 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 - type: string - optional: - description: Specify whether the ConfigMap or its - key must be defined - type: boolean - required: - - key - type: object - fieldRef: - description: Selects a field of the pod - 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 - resourceFieldRef: - description: 'Selects a resource of the container: only - resources limits and requests 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 - 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 - type: string - optional: - description: Specify whether the Secret or its key - must be defined - type: boolean - required: - - key - type: object - type: object - required: - - name - type: object - type: array - serviceNaming: - enum: - - bdb_name - - redis-port + description: Selector for nodes that could fit Redis Enterprise pod + type: object + ocspConfiguration: + description: An API object that represents the cluster's OCSP configuration. + To enable OCSP, the cluster's proxy certificate should contain the + OCSP responder URL. + properties: + ocspFunctionality: + description: Whether to enable/disable OCSP mechanism for the + cluster. + type: boolean + queryFrequency: + description: Determines the interval (in seconds) in which the + control plane will poll the OCSP responder for a new status + for the server certificate. Minimum value is 60. Maximum value + is 86400. + type: integer + recoveryFrequency: + description: Determines the interval (in seconds) in which the + control plane will poll the OCSP responder for a new status + for the server certificate when the current staple is invalid. + Minimum value is 60. Maximum value is 86400. + type: integer + recoveryMaxTries: + description: Determines the maximum number for the OCSP recovery + attempts. After max number of tries passed, the control plane + will revert back to the regular frequency. Minimum value is + 1. Maximum value is 100. + type: integer + responseTimeout: + description: Determines the time interval (in seconds) for which + the request waits for a response from the OCSP responder. Minimum + value is 1. Maximum value is 60. + type: integer + type: object + nodes: + description: Number of Redis Enterprise nodes (pods) + format: int32 + type: integer + persistentSpec: + description: Specification for Redis Enterprise Cluster persistence + properties: + enabled: + description: Whether to add persistent volume to Redis Enterprise + pods + type: boolean + storageClassName: + description: Storage class for persistent volume in Redis Enterprise + pods Leave empty to use the default + type: string + volumeSize: + 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 + type: object + podAnnotations: + additionalProperties: type: string - type: object - sideContainersSpec: - items: + description: pod annotations + type: object + podAntiAffinity: + description: 'Override for the default anti-affinity rules of the Redis + Enterprise pods. + More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#an-example-of-a-pod-that-uses-pod-affinity' properties: - args: - items: - type: string - type: array - command: - items: - type: string - type: array - env: + preferredDuringSchedulingIgnoredDuringExecution: items: properties: - name: - type: string - value: - type: string - valueFrom: + podAffinityTerm: properties: - configMapKeyRef: + labelSelector: properties: - key: - type: string - name: - type: string - optional: - type: boolean - required: - - key + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object type: object - fieldRef: + namespaceSelector: properties: - apiVersion: - type: string - fieldPath: - type: string - required: - - fieldPath - type: object - resourceFieldRef: - properties: - containerName: - type: string - divisor: - 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 - resource: - type: string - required: - - resource - type: object - secretKeyRef: - properties: - key: - type: string - name: - type: string - optional: - type: boolean - required: - - key + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey type: object + weight: + format: int32 + type: integer required: - - name + - podAffinityTerm + - weight type: object type: array - envFrom: + requiredDuringSchedulingIgnoredDuringExecution: items: properties: - configMapRef: + labelSelector: properties: - name: - type: string - optional: - type: boolean + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object type: object - prefix: - type: string - secretRef: + namespaceSelector: properties: - name: - type: string - optional: - type: boolean + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey type: object type: array - image: - type: string - imagePullPolicy: - type: string - lifecycle: + type: object + podSecurityPolicyName: + description: Name of pod security policy to use on pods See https://kubernetes.io/docs/concepts/policy/pod-security-policy/ + type: string + podStartingPolicy: + description: Mitigation setting for STS pods stuck in "ContainerCreating" + properties: + enabled: + description: Whether to detect and attempt to mitigate pod startup + issues + type: boolean + startingThresholdSeconds: + description: Time in seconds to wait for a pod to be stuck while + starting up before action is taken. If set to 0, will be treated + as if disabled. + format: int32 + type: integer + required: + - enabled + - startingThresholdSeconds + type: object + podTolerations: + description: 'Tolerations that are added to all managed pods. More + information: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/' + items: + properties: + effect: + type: string + key: + type: string + operator: + type: string + tolerationSeconds: + format: int64 + type: integer + value: + type: string + type: object + type: array + priorityClassName: + description: Adds the priority class to pods managed by the operator + type: string + pullSecrets: + description: 'PullSecrets is an optional list of references to secrets + in the same namespace to use for pulling any of the images. If specified, + these secrets will be passed to individual puller implementations + for them to use. More info: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/' + items: + properties: + name: + description: 'Secret name' + type: string + type: object + type: array + rackAwarenessNodeLabel: + description: Node label that specifies rack ID - if specified, will + create rack aware cluster. Rack awareness requires node label must + exist on all nodes. Additionally, operator needs a special cluster + role with permission to list nodes. + type: string + redisEnterpriseAdditionalPodSpecAttributes: + description: ADVANCED USAGE USE AT YOUR OWN RISK - specify pod attributes + that are required for the statefulset - Redis Enterprise pods. Pod + attributes managed by the operator might override these settings. + Also make sure the attributes are supported by the K8s version running + on the cluster - the operator does not validate that. + properties: + activeDeadlineSeconds: + format: int64 + type: integer + affinity: properties: - postStart: + nodeAffinity: properties: - exec: - properties: - command: - items: - type: string - type: array - type: object - httpGet: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + preference: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: properties: - host: - type: string - httpHeaders: + nodeSelectorTerms: items: properties: - name: - type: string - value: - type: string - required: - - name - - value + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array type: object type: array - path: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - scheme: - type: string required: - - port - type: object - tcpSocket: - properties: - host: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - required: - - port + - nodeSelectorTerms type: object type: object - preStop: + podAffinity: properties: - exec: - properties: - command: - items: - type: string - type: array - type: object - httpGet: - properties: - host: - type: string - httpHeaders: - items: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: properties: - name: - type: string - value: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: type: string required: - - name - - value + - topologyKey type: object - type: array - path: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - scheme: - type: string - required: - - port - type: object - tcpSocket: - properties: - host: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - required: - - port - type: object - type: object - type: object - livenessProbe: - properties: - exec: - properties: - command: - items: - type: string + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object type: array - type: object - failureThreshold: - format: int32 - type: integer - httpGet: - properties: - host: - type: string - httpHeaders: + requiredDuringSchedulingIgnoredDuringExecution: items: properties: - name: - type: string - value: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: type: string required: - - name - - value + - topologyKey type: object type: array - path: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - scheme: - type: string - required: - - port type: object - initialDelaySeconds: - format: int32 - type: integer - periodSeconds: - format: int32 - type: integer - successThreshold: - format: int32 - type: integer - tcpSocket: + podAntiAffinity: properties: - host: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - required: - - port - type: object - timeoutSeconds: - format: int32 - type: integer - type: object - name: - type: string - ports: - items: - properties: - containerPort: - format: int32 - type: integer - hostIP: - type: string - hostPort: - format: int32 - type: integer - name: - type: string - protocol: - type: string - required: - - containerPort - type: object - type: array - readinessProbe: - properties: - exec: - properties: - command: - items: - type: string - type: array - type: object - failureThreshold: - format: int32 - type: integer - httpGet: - properties: - host: - type: string - httpHeaders: + preferredDuringSchedulingIgnoredDuringExecution: items: properties: - name: - type: string - value: - type: string + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer required: - - name - - value + - podAffinityTerm + - weight type: object type: array - path: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - scheme: - type: string - required: - - port - type: object - initialDelaySeconds: - format: int32 - type: integer - periodSeconds: - format: int32 - type: integer - successThreshold: - format: int32 - type: integer - tcpSocket: - properties: - host: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - required: - - port - type: object - timeoutSeconds: - format: int32 - type: integer - type: object - 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 - 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 - type: object - type: object - securityContext: - properties: - allowPrivilegeEscalation: - type: boolean - capabilities: - properties: - add: - items: - type: string - type: array - drop: - items: - type: string - type: array - type: object - privileged: - type: boolean - procMount: - type: string - readOnlyRootFilesystem: - type: boolean - runAsGroup: - format: int64 - type: integer - runAsNonRoot: - type: boolean - runAsUser: - format: int64 - type: integer - seLinuxOptions: - properties: - level: - type: string - role: - type: string - type: - type: string - user: - type: string - type: object - windowsOptions: - properties: - gmsaCredentialSpec: - type: string - gmsaCredentialSpecName: - type: string - runAsUserName: - type: string - type: object - type: object - startupProbe: - properties: - exec: - properties: - command: - items: - type: string - type: array - type: object - failureThreshold: - format: int32 - type: integer - httpGet: - properties: - host: - type: string - httpHeaders: + requiredDuringSchedulingIgnoredDuringExecution: items: properties: - name: - type: string - value: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: type: string required: - - name - - value + - topologyKey type: object type: array - path: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - scheme: - type: string - required: - - port - type: object - initialDelaySeconds: - format: int32 - type: integer - periodSeconds: - format: int32 - type: integer - successThreshold: - format: int32 - type: integer - tcpSocket: - properties: - host: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - required: - - port type: object - timeoutSeconds: - format: int32 - type: integer type: object - stdin: - type: boolean - stdinOnce: - type: boolean - terminationMessagePath: - type: string - terminationMessagePolicy: - type: string - tty: + automountServiceAccountToken: type: boolean - volumeDevices: - items: - properties: - devicePath: + dnsConfig: + properties: + nameservers: + items: type: string - name: + type: array + options: + items: + properties: + name: + type: string + value: + type: string + type: object + type: array + searches: + items: type: string - required: - - devicePath - - name - type: object - type: array - volumeMounts: + type: array + type: object + dnsPolicy: + type: string + enableServiceLinks: + type: boolean + ephemeralContainers: items: properties: - mountPath: - type: string - mountPropagation: - type: string - name: - type: string - readOnly: - type: boolean - subPath: + args: + items: + type: string + type: array + command: + items: + type: string + type: array + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + resourceFieldRef: + properties: + containerName: + type: string + divisor: + 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 + resource: + type: string + required: + - resource + type: object + secretKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + envFrom: + items: + properties: + configMapRef: + properties: + name: + type: string + optional: + type: boolean + type: object + prefix: + type: string + secretRef: + properties: + name: + type: string + optional: + type: boolean + type: object + type: object + type: array + image: type: string - subPathExpr: + imagePullPolicy: type: string - required: - - mountPath - - name - type: object - type: array - workingDir: - type: string - required: - - name - type: object - type: array - slaveHA: - description: Slave high availability mechanism configuration. - properties: - slaveHAGracePeriod: - description: Time in seconds between when a node fails, and when - slave high availability mechanism starts relocating shards. If - set to 0, will not affect cluster configuration. - format: int32 - type: integer - type: object - uiAnnotations: - additionalProperties: - type: string - description: Annotations for Redis Enterprise UI service - type: object - uiServiceType: - description: Type of service used to expose Redis Enterprise UI (https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types) - enum: - - ClusterIP - - NodePort - - LoadBalancer - - ExternalName - type: string - upgradeSpec: - description: Specification for upgrades of Redis Enterprise - properties: - autoUpgradeRedisEnterprise: - description: Whether to upgrade Redis Enterprise automatically when - operator is upgraded - type: boolean - required: - - autoUpgradeRedisEnterprise - type: object - username: - description: Username for the admin user of Redis Enterprise - type: string - volumes: - description: additional volumes - items: - description: 'Volume represents a named volume in a pod that may be - accessed by any container in the pod. - More info: https://kubernetes.io/docs/concepts/storage/volumes' - properties: - awsElasticBlockStore: - properties: - fsType: - type: string - partition: - format: int32 - type: integer - readOnly: - type: boolean - volumeID: - type: string - required: - - volumeID - type: object - azureDisk: - properties: - cachingMode: - type: string - diskName: - type: string - diskURI: - type: string - fsType: - type: string - kind: - type: string - readOnly: - type: boolean - required: - - diskName - - diskURI - type: object - azureFile: - properties: - readOnly: - type: boolean - secretName: - type: string - shareName: - type: string - required: - - secretName - - shareName - type: object - cephfs: - properties: - monitors: - items: - type: string - type: array - path: - type: string - readOnly: - type: boolean - secretFile: - type: string - secretRef: - properties: - name: - type: string - type: object - user: - type: string - required: - - monitors - type: object - cinder: - properties: - fsType: - type: string - readOnly: - type: boolean - secretRef: - properties: - name: - type: string - type: object - volumeID: - type: string - required: - - volumeID - type: object - configMap: - properties: - defaultMode: - format: int32 - type: integer - items: - items: + lifecycle: properties: - key: - type: string - mode: + postStart: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + type: object + livenessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + failureThreshold: + format: int32 + type: integer + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: format: int32 type: integer - path: - type: string - required: - - key - - path type: object - type: array - name: - type: string - optional: - type: boolean - type: object - csi: - properties: - driver: - type: string - fsType: - type: string - nodePublishSecretRef: - properties: - name: - type: string - type: object - readOnly: - type: boolean - volumeAttributes: - additionalProperties: + name: type: string - type: object - required: - - driver - type: object - downwardAPI: - properties: - defaultMode: - format: int32 - type: integer - items: - items: + ports: + items: + properties: + containerPort: + format: int32 + type: integer + hostIP: + type: string + hostPort: + format: int32 + type: integer + name: + type: string + protocol: + default: TCP + type: string + required: + - containerPort + type: object + type: array + readinessProbe: properties: - fieldRef: + exec: properties: - apiVersion: + command: + items: + type: string + type: array + type: object + failureThreshold: + format: int32 + type: integer + httpGet: + properties: + host: type: string - fieldPath: + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: type: string required: - - fieldPath + - port type: object - mode: + initialDelaySeconds: format: int32 type: integer - path: - type: string - resourceFieldRef: + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: properties: - containerName: + host: type: string - divisor: + port: 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 - resource: - type: string required: - - resource + - port type: object - required: - - path + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer type: object - type: array - type: object - emptyDir: - properties: - medium: - type: string - sizeLimit: - 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 - type: object - fc: - properties: - fsType: - type: string - lun: - format: int32 - type: integer - readOnly: - type: boolean - targetWWNs: - items: - type: string - type: array - wwids: - items: - type: string - type: array - type: object - flexVolume: - properties: - driver: - type: string - fsType: - type: string - options: - additionalProperties: - type: string - type: object - readOnly: - type: boolean - secretRef: - properties: - name: - type: string - type: object - required: - - driver - type: object - flocker: - properties: - datasetName: - type: string - datasetUUID: - type: string - type: object - gcePersistentDisk: - properties: - fsType: - type: string - partition: - format: int32 - type: integer - pdName: - type: string - readOnly: - type: boolean - required: - - pdName - type: object - gitRepo: - properties: - directory: - type: string - repository: - type: string - revision: - type: string - required: - - repository - type: object - glusterfs: - properties: - endpoints: - type: string - path: - type: string - readOnly: - type: boolean - required: - - endpoints - - path - type: object - hostPath: - properties: - path: - type: string - type: - type: string - required: - - path - type: object - iscsi: - properties: - chapAuthDiscovery: - type: boolean - chapAuthSession: - type: boolean - fsType: - type: string - initiatorName: - type: string - iqn: - type: string - iscsiInterface: - type: string - lun: - format: int32 - type: integer - portals: - items: - type: string - type: array - readOnly: - type: boolean - secretRef: - properties: - name: - type: string - type: object - targetPortal: - type: string - required: - - iqn - - lun - - targetPortal - type: object - name: - type: string - nfs: - properties: - path: - type: string - readOnly: - type: boolean - server: - type: string - required: - - path - - server - type: object - persistentVolumeClaim: - properties: - claimName: - type: string - readOnly: - type: boolean - required: - - claimName - type: object - photonPersistentDisk: - properties: - fsType: - type: string - pdID: - type: string - required: - - pdID - type: object - portworxVolume: - properties: - fsType: - type: string - readOnly: - type: boolean - volumeID: - type: string - required: - - volumeID - type: object - projected: - properties: - defaultMode: - format: int32 - type: integer - sources: - items: + resources: properties: - configMap: + 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 + 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 + type: object + type: object + securityContext: + properties: + allowPrivilegeEscalation: + type: boolean + capabilities: properties: - items: + add: items: - properties: - key: - type: string - mode: - format: int32 - type: integer - path: - type: string - required: - - key - - path - type: object + type: string type: array - name: + drop: + items: + type: string + type: array + type: object + privileged: + type: boolean + procMount: + type: string + readOnlyRootFilesystem: + type: boolean + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: type: string - optional: + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: type: boolean + runAsUserName: + type: string type: object - downwardAPI: + type: object + startupProbe: + properties: + exec: properties: - items: + command: items: - properties: - fieldRef: - properties: - apiVersion: - type: string - fieldPath: - type: string - required: - - fieldPath - type: object - mode: - format: int32 - type: integer - path: - type: string - resourceFieldRef: - properties: - containerName: - type: string - divisor: - 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 - resource: - type: string - required: - - resource - type: object - required: - - path - type: object + type: string type: array type: object - secret: + failureThreshold: + format: int32 + type: integer + httpGet: properties: - items: + host: + type: string + httpHeaders: items: properties: - key: + name: type: string - mode: - format: int32 - type: integer - path: + value: type: string required: - - key - - path + - name + - value type: object type: array - name: + path: type: string - optional: - type: boolean + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port type: object - serviceAccountToken: + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: properties: - audience: - type: string - expirationSeconds: - format: int64 - type: integer - path: + host: type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true required: - - path + - port type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer type: object - type: array - required: - - sources - type: object - quobyte: - properties: - group: - type: string - readOnly: - type: boolean - registry: - type: string - tenant: - type: string - user: - type: string - volume: - type: string - required: - - registry - - volume - type: object - rbd: - properties: - fsType: - type: string - image: - type: string - keyring: - type: string - monitors: - items: + stdin: + type: boolean + stdinOnce: + type: boolean + targetContainerName: type: string - type: array - pool: - type: string - readOnly: - type: boolean - secretRef: - properties: - name: - type: string - type: object - user: - type: string - required: - - image - - monitors - type: object - scaleIO: - properties: - fsType: - type: string - gateway: - type: string - protectionDomain: - type: string - readOnly: - type: boolean - secretRef: - properties: - name: - type: string - type: object - sslEnabled: - type: boolean - storageMode: - type: string - storagePool: - type: string - system: - type: string - volumeName: - type: string - required: - - gateway - - secretRef - - system + terminationMessagePath: + type: string + terminationMessagePolicy: + type: string + tty: + type: boolean + volumeDevices: + items: + properties: + devicePath: + type: string + name: + type: string + required: + - devicePath + - name + type: object + type: array + volumeMounts: + items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: array + workingDir: + type: string + required: + - name + type: object + type: array + hostAliases: + items: + properties: + hostnames: + items: + type: string + type: array + ip: + type: string + type: object + type: array + hostIPC: + type: boolean + hostNetwork: + type: boolean + hostPID: + type: boolean + hostname: + type: string + imagePullSecrets: + items: + properties: + name: + type: string + type: object + type: array + initContainers: + items: + properties: + args: + items: + type: string + type: array + command: + items: + type: string + type: array + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + resourceFieldRef: + properties: + containerName: + type: string + divisor: + 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 + resource: + type: string + required: + - resource + type: object + secretKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + envFrom: + items: + properties: + configMapRef: + properties: + name: + type: string + optional: + type: boolean + type: object + prefix: + type: string + secretRef: + properties: + name: + type: string + optional: + type: boolean + type: object + type: object + type: array + image: + type: string + imagePullPolicy: + type: string + lifecycle: + properties: + postStart: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + type: object + livenessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + failureThreshold: + format: int32 + type: integer + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + name: + type: string + ports: + items: + properties: + containerPort: + format: int32 + type: integer + hostIP: + type: string + hostPort: + format: int32 + type: integer + name: + type: string + protocol: + default: TCP + type: string + required: + - containerPort + type: object + type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + failureThreshold: + format: int32 + type: integer + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + 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 + 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 + type: object + type: object + securityContext: + properties: + allowPrivilegeEscalation: + type: boolean + capabilities: + properties: + add: + items: + type: string + type: array + drop: + items: + type: string + type: array + type: object + privileged: + type: boolean + procMount: + type: string + readOnlyRootFilesystem: + type: boolean + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + startupProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + failureThreshold: + format: int32 + type: integer + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + stdin: + type: boolean + stdinOnce: + type: boolean + terminationMessagePath: + type: string + terminationMessagePolicy: + type: string + tty: + type: boolean + volumeDevices: + items: + properties: + devicePath: + type: string + name: + type: string + required: + - devicePath + - name + type: object + type: array + volumeMounts: + items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: array + workingDir: + type: string + required: + - name + type: object + type: array + nodeName: + type: string + nodeSelector: + additionalProperties: + type: string + type: object + x-kubernetes-map-type: atomic + overhead: + 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 + type: object + preemptionPolicy: + type: string + priority: + format: int32 + type: integer + priorityClassName: + type: string + readinessGates: + items: + properties: + conditionType: + type: string + required: + - conditionType + type: object + type: array + restartPolicy: + type: string + runtimeClassName: + type: string + schedulerName: + type: string + securityContext: + properties: + fsGroup: + format: int64 + type: integer + fsGroupChangePolicy: + type: string + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + supplementalGroups: + items: + format: int64 + type: integer + type: array + sysctls: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + serviceAccount: + type: string + serviceAccountName: + type: string + setHostnameAsFQDN: + type: boolean + shareProcessNamespace: + type: boolean + subdomain: + type: string + terminationGracePeriodSeconds: + format: int64 + type: integer + tolerations: + items: + properties: + effect: + type: string + key: + type: string + operator: + type: string + tolerationSeconds: + format: int64 + type: integer + value: + type: string + type: object + type: array + topologySpreadConstraints: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + maxSkew: + format: int32 + type: integer + topologyKey: + type: string + whenUnsatisfiable: + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + x-kubernetes-list-map-keys: + - topologyKey + - whenUnsatisfiable + x-kubernetes-list-type: map + volumes: + items: + properties: + awsElasticBlockStore: + properties: + fsType: + type: string + partition: + format: int32 + type: integer + readOnly: + type: boolean + volumeID: + type: string + required: + - volumeID + type: object + azureDisk: + properties: + cachingMode: + type: string + diskName: + type: string + diskURI: + type: string + fsType: + type: string + kind: + type: string + readOnly: + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + properties: + readOnly: + type: boolean + secretName: + type: string + shareName: + type: string + required: + - secretName + - shareName + type: object + cephfs: + properties: + monitors: + items: + type: string + type: array + path: + type: string + readOnly: + type: boolean + secretFile: + type: string + secretRef: + properties: + name: + type: string + type: object + user: + type: string + required: + - monitors + type: object + cinder: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + volumeID: + type: string + required: + - volumeID + type: object + configMap: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + csi: + properties: + driver: + type: string + fsType: + type: string + nodePublishSecretRef: + properties: + name: + type: string + type: object + readOnly: + type: boolean + volumeAttributes: + additionalProperties: + type: string + type: object + required: + - driver + type: object + downwardAPI: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + 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 + resource: + type: string + required: + - resource + type: object + required: + - path + type: object + type: array + type: object + emptyDir: + properties: + medium: + type: string + sizeLimit: + 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 + type: object + ephemeral: + properties: + volumeClaimTemplate: + properties: + metadata: + type: object + spec: + properties: + accessModes: + items: + type: string + type: array + dataSource: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + required: + - kind + - name + type: object + dataSourceRef: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + required: + - kind + - name + type: object + 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 + 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 + type: object + type: object + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + storageClassName: + type: string + volumeMode: + type: string + volumeName: + type: string + type: object + required: + - spec + type: object + type: object + fc: + properties: + fsType: + type: string + lun: + format: int32 + type: integer + readOnly: + type: boolean + targetWWNs: + items: + type: string + type: array + wwids: + items: + type: string + type: array + type: object + flexVolume: + properties: + driver: + type: string + fsType: + type: string + options: + additionalProperties: + type: string + type: object + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + required: + - driver + type: object + flocker: + properties: + datasetName: + type: string + datasetUUID: + type: string + type: object + gcePersistentDisk: + properties: + fsType: + type: string + partition: + format: int32 + type: integer + pdName: + type: string + readOnly: + type: boolean + required: + - pdName + type: object + gitRepo: + properties: + directory: + type: string + repository: + type: string + revision: + type: string + required: + - repository + type: object + glusterfs: + properties: + endpoints: + type: string + path: + type: string + readOnly: + type: boolean + required: + - endpoints + - path + type: object + hostPath: + properties: + path: + type: string + type: + type: string + required: + - path + type: object + iscsi: + properties: + chapAuthDiscovery: + type: boolean + chapAuthSession: + type: boolean + fsType: + type: string + initiatorName: + type: string + iqn: + type: string + iscsiInterface: + type: string + lun: + format: int32 + type: integer + portals: + items: + type: string + type: array + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + targetPortal: + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + type: string + nfs: + properties: + path: + type: string + readOnly: + type: boolean + server: + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + properties: + claimName: + type: string + readOnly: + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + properties: + fsType: + type: string + pdID: + type: string + required: + - pdID + type: object + portworxVolume: + properties: + fsType: + type: string + readOnly: + type: boolean + volumeID: + type: string + required: + - volumeID + type: object + projected: + properties: + defaultMode: + format: int32 + type: integer + sources: + items: + properties: + configMap: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + downwardAPI: + properties: + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + 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 + resource: + type: string + required: + - resource + type: object + required: + - path + type: object + type: array + type: object + secret: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + serviceAccountToken: + properties: + audience: + type: string + expirationSeconds: + format: int64 + type: integer + path: + type: string + required: + - path + type: object + type: object + type: array + type: object + quobyte: + properties: + group: + type: string + readOnly: + type: boolean + registry: + type: string + tenant: + type: string + user: + type: string + volume: + type: string + required: + - registry + - volume + type: object + rbd: + properties: + fsType: + type: string + image: + type: string + keyring: + type: string + monitors: + items: + type: string + type: array + pool: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + user: + type: string + required: + - image + - monitors + type: object + scaleIO: + properties: + fsType: + type: string + gateway: + type: string + protectionDomain: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + sslEnabled: + type: boolean + storageMode: + type: string + storagePool: + type: string + system: + type: string + volumeName: + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + optional: + type: boolean + secretName: + type: string + type: object + storageos: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + volumeName: + type: string + volumeNamespace: + type: string + type: object + vsphereVolume: + properties: + fsType: + type: string + storagePolicyID: + type: string + storagePolicyName: + type: string + volumePath: + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + type: object + redisEnterpriseImageSpec: + description: Specification for Redis Enterprise container image + properties: + imagePullPolicy: + description: PullPolicy describes a policy for if/when to pull a + container image + type: string + repository: + description: Repository + type: string + versionTag: + type: string + type: object + redisEnterpriseNodeResources: + description: Compute resource requirements for Redis Enterprise containers + 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-compute-resources-container/' + 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-compute-resources-container/' + type: object + type: object + redisEnterpriseServicesConfiguration: + description: RS Cluster optional services settings + properties: + cmServer: + properties: + operatingMode: + description: Whether to enable/disable the CM server + enum: + - enabled + - disabled + type: string + required: + - operatingMode + type: object + crdbCoordinator: + properties: + operatingMode: + description: Whether to enable/disable the crdb coordinator + process + enum: + - enabled + - disabled + type: string + required: + - operatingMode + type: object + crdbWorker: + properties: + operatingMode: + description: Whether to enable/disable the crdb worker processes + enum: + - enabled + - disabled + type: string + required: + - operatingMode + type: object + mdnsServer: + properties: + operatingMode: + description: Whether to enable/disable the Multicast DNS server + enum: + - enabled + - disabled + type: string + required: + - operatingMode + type: object + pdnsServer: + properties: + operatingMode: + description: Whether to enable/disable the pdns server + enum: + - enabled + - disabled + type: string + required: + - operatingMode + type: object + saslauthd: + properties: + operatingMode: + description: Whether to enable/disable the saslauthd service + enum: + - enabled + - disabled + type: string + required: + - operatingMode + type: object + statsArchiver: + properties: + operatingMode: + description: Whether to enable/disable the stats archiver service + enum: + - enabled + - disabled + type: string + required: + - operatingMode + type: object + type: object + redisEnterpriseServicesRiggerImageSpec: + description: Specification for Services Rigger container image + properties: + imagePullPolicy: + description: PullPolicy describes a policy for if/when to pull a + container image + type: string + repository: + description: Repository + type: string + versionTag: + type: string + type: object + redisEnterpriseServicesRiggerResources: + description: Compute resource requirements for Services Rigger pod + 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-compute-resources-container/' + 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-compute-resources-container/' + type: object + type: object + redisEnterpriseTerminationGracePeriodSeconds: + description: The TerminationGracePeriodSeconds value for the (STS created) REC pods + format: int64 + type: integer + redisEnterpriseVolumeMounts: + description: 'additional volume mounts within the redis enterprise containers. + More info: https://kubernetes.io/docs/concepts/storage/volumes/' + items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: array + redisUpgradePolicy: + description: 'Redis upgrade policy to be set on the Redis Enterprise + Cluster. Possible values: major/latest This value is used by the cluster + to choose the Redis version of the database when an upgrade is performed. + The Redis Enterprise Cluster includes multiple versions of OSS Redis + that can be used for databases.' + enum: + - major + - latest + type: string + serviceAccountName: + description: Name of the service account to use + type: string + servicesRiggerSpec: + description: Specification for service rigger + properties: + databaseServiceType: + description: Service types for access to databases. should be a + comma separated list. The possible values are cluster_ip, headless + and load_balancer. + type: string + extraEnvVars: + items: + description: 'EnvVar represents an environment variable present + in a Container. + More info: https://kubernetes.io/docs/tasks/inject-data-application/environment-variable-expose-pod-information/' + properties: + name: + description: Name of the environment variable. + type: string + value: + 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 + type: string + optional: + description: Specify whether the ConfigMap or its + key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: Selects a field of the pod + 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 + resourceFieldRef: + description: 'Selects a resource of the container: only + resources limits and requests 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 + 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 + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + serviceNaming: + enum: + - bdb_name + - redis-port + type: string + servicesRiggerAdditionalPodSpecAttributes: + description: ADVANCED USAGE USE AT YOUR OWN RISK - specify pod attributes + that are required for the rigger deployment pod. Pod attributes + managed by the operator might override these settings (Containers, + serviceAccountName, podTolerations, ImagePullSecrets, nodeSelector, + PriorityClassName, PodSecurityContext). Also make sure the attributes are supported + by the K8s version running on the cluster - the operator does + not validate that. + properties: + activeDeadlineSeconds: + format: int64 + type: integer + affinity: + properties: + nodeAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + preference: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + properties: + nodeSelectorTerms: + items: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + type: array + required: + - nodeSelectorTerms + type: object + type: object + podAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + automountServiceAccountToken: + type: boolean + dnsConfig: + properties: + nameservers: + items: + type: string + type: array + options: + items: + properties: + name: + type: string + value: + type: string + type: object + type: array + searches: + items: + type: string + type: array + type: object + dnsPolicy: + type: string + enableServiceLinks: + type: boolean + ephemeralContainers: + items: + properties: + args: + items: + type: string + type: array + command: + items: + type: string + type: array + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + resourceFieldRef: + properties: + containerName: + type: string + divisor: + 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 + resource: + type: string + required: + - resource + type: object + secretKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + envFrom: + items: + properties: + configMapRef: + properties: + name: + type: string + optional: + type: boolean + type: object + prefix: + type: string + secretRef: + properties: + name: + type: string + optional: + type: boolean + type: object + type: object + type: array + image: + type: string + imagePullPolicy: + type: string + lifecycle: + properties: + postStart: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + type: object + livenessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + failureThreshold: + format: int32 + type: integer + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + name: + type: string + ports: + items: + properties: + containerPort: + format: int32 + type: integer + hostIP: + type: string + hostPort: + format: int32 + type: integer + name: + type: string + protocol: + default: TCP + type: string + required: + - containerPort + type: object + type: array + readinessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + failureThreshold: + format: int32 + type: integer + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + 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 + 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 + type: object + type: object + securityContext: + properties: + allowPrivilegeEscalation: + type: boolean + capabilities: + properties: + add: + items: + type: string + type: array + drop: + items: + type: string + type: array + type: object + privileged: + type: boolean + procMount: + type: string + readOnlyRootFilesystem: + type: boolean + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + startupProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + failureThreshold: + format: int32 + type: integer + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + stdin: + type: boolean + stdinOnce: + type: boolean + targetContainerName: + type: string + terminationMessagePath: + type: string + terminationMessagePolicy: + type: string + tty: + type: boolean + volumeDevices: + items: + properties: + devicePath: + type: string + name: + type: string + required: + - devicePath + - name + type: object + type: array + volumeMounts: + items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: array + workingDir: + type: string + required: + - name + type: object + type: array + hostAliases: + items: + properties: + hostnames: + items: + type: string + type: array + ip: + type: string + type: object + type: array + hostIPC: + type: boolean + hostNetwork: + type: boolean + hostPID: + type: boolean + hostname: + type: string + imagePullSecrets: + items: + properties: + name: + type: string + type: object + type: array + initContainers: + items: + properties: + args: + items: + type: string + type: array + command: + items: + type: string + type: array + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + resourceFieldRef: + properties: + containerName: + type: string + divisor: + 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 + resource: + type: string + required: + - resource + type: object + secretKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + envFrom: + items: + properties: + configMapRef: + properties: + name: + type: string + optional: + type: boolean + type: object + prefix: + type: string + secretRef: + properties: + name: + type: string + optional: + type: boolean + type: object + type: object + type: array + image: + type: string + imagePullPolicy: + type: string + lifecycle: + properties: + postStart: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + type: object + livenessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + failureThreshold: + format: int32 + type: integer + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + name: + type: string + ports: + items: + properties: + containerPort: + format: int32 + type: integer + hostIP: + type: string + hostPort: + format: int32 + type: integer + name: + type: string + protocol: + default: TCP + type: string + required: + - containerPort + type: object + type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + failureThreshold: + format: int32 + type: integer + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + 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 + 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 + type: object + type: object + securityContext: + properties: + allowPrivilegeEscalation: + type: boolean + capabilities: + properties: + add: + items: + type: string + type: array + drop: + items: + type: string + type: array + type: object + privileged: + type: boolean + procMount: + type: string + readOnlyRootFilesystem: + type: boolean + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + startupProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + failureThreshold: + format: int32 + type: integer + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + stdin: + type: boolean + stdinOnce: + type: boolean + terminationMessagePath: + type: string + terminationMessagePolicy: + type: string + tty: + type: boolean + volumeDevices: + items: + properties: + devicePath: + type: string + name: + type: string + required: + - devicePath + - name + type: object + type: array + volumeMounts: + items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: array + workingDir: + type: string + required: + - name + type: object + type: array + nodeName: + type: string + nodeSelector: + additionalProperties: + type: string + type: object + x-kubernetes-map-type: atomic + overhead: + 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 + type: object + preemptionPolicy: + type: string + priority: + format: int32 + type: integer + priorityClassName: + type: string + readinessGates: + items: + properties: + conditionType: + type: string + required: + - conditionType + type: object + type: array + restartPolicy: + type: string + runtimeClassName: + type: string + schedulerName: + type: string + securityContext: + properties: + fsGroup: + format: int64 + type: integer + fsGroupChangePolicy: + type: string + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + supplementalGroups: + items: + format: int64 + type: integer + type: array + sysctls: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + serviceAccount: + type: string + serviceAccountName: + type: string + setHostnameAsFQDN: + type: boolean + shareProcessNamespace: + type: boolean + subdomain: + type: string + terminationGracePeriodSeconds: + format: int64 + type: integer + tolerations: + items: + properties: + effect: + type: string + key: + type: string + operator: + type: string + tolerationSeconds: + format: int64 + type: integer + value: + type: string + type: object + type: array + topologySpreadConstraints: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + maxSkew: + format: int32 + type: integer + topologyKey: + type: string + whenUnsatisfiable: + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + x-kubernetes-list-map-keys: + - topologyKey + - whenUnsatisfiable + x-kubernetes-list-type: map + volumes: + items: + properties: + awsElasticBlockStore: + properties: + fsType: + type: string + partition: + format: int32 + type: integer + readOnly: + type: boolean + volumeID: + type: string + required: + - volumeID + type: object + azureDisk: + properties: + cachingMode: + type: string + diskName: + type: string + diskURI: + type: string + fsType: + type: string + kind: + type: string + readOnly: + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + properties: + readOnly: + type: boolean + secretName: + type: string + shareName: + type: string + required: + - secretName + - shareName + type: object + cephfs: + properties: + monitors: + items: + type: string + type: array + path: + type: string + readOnly: + type: boolean + secretFile: + type: string + secretRef: + properties: + name: + type: string + type: object + user: + type: string + required: + - monitors + type: object + cinder: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + volumeID: + type: string + required: + - volumeID + type: object + configMap: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + csi: + properties: + driver: + type: string + fsType: + type: string + nodePublishSecretRef: + properties: + name: + type: string + type: object + readOnly: + type: boolean + volumeAttributes: + additionalProperties: + type: string + type: object + required: + - driver + type: object + downwardAPI: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + 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 + resource: + type: string + required: + - resource + type: object + required: + - path + type: object + type: array + type: object + emptyDir: + properties: + medium: + type: string + sizeLimit: + 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 + type: object + ephemeral: + properties: + volumeClaimTemplate: + properties: + metadata: + type: object + spec: + properties: + accessModes: + items: + type: string + type: array + dataSource: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + required: + - kind + - name + type: object + dataSourceRef: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + required: + - kind + - name + type: object + 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 + 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 + type: object + type: object + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + storageClassName: + type: string + volumeMode: + type: string + volumeName: + type: string + type: object + required: + - spec + type: object + type: object + fc: + properties: + fsType: + type: string + lun: + format: int32 + type: integer + readOnly: + type: boolean + targetWWNs: + items: + type: string + type: array + wwids: + items: + type: string + type: array + type: object + flexVolume: + properties: + driver: + type: string + fsType: + type: string + options: + additionalProperties: + type: string + type: object + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + required: + - driver + type: object + flocker: + properties: + datasetName: + type: string + datasetUUID: + type: string + type: object + gcePersistentDisk: + properties: + fsType: + type: string + partition: + format: int32 + type: integer + pdName: + type: string + readOnly: + type: boolean + required: + - pdName + type: object + gitRepo: + properties: + directory: + type: string + repository: + type: string + revision: + type: string + required: + - repository + type: object + glusterfs: + properties: + endpoints: + type: string + path: + type: string + readOnly: + type: boolean + required: + - endpoints + - path + type: object + hostPath: + properties: + path: + type: string + type: + type: string + required: + - path + type: object + iscsi: + properties: + chapAuthDiscovery: + type: boolean + chapAuthSession: + type: boolean + fsType: + type: string + initiatorName: + type: string + iqn: + type: string + iscsiInterface: + type: string + lun: + format: int32 + type: integer + portals: + items: + type: string + type: array + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + targetPortal: + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + type: string + nfs: + properties: + path: + type: string + readOnly: + type: boolean + server: + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + properties: + claimName: + type: string + readOnly: + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + properties: + fsType: + type: string + pdID: + type: string + required: + - pdID + type: object + portworxVolume: + properties: + fsType: + type: string + readOnly: + type: boolean + volumeID: + type: string + required: + - volumeID + type: object + projected: + properties: + defaultMode: + format: int32 + type: integer + sources: + items: + properties: + configMap: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + downwardAPI: + properties: + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + 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 + resource: + type: string + required: + - resource + type: object + required: + - path + type: object + type: array + type: object + secret: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + serviceAccountToken: + properties: + audience: + type: string + expirationSeconds: + format: int64 + type: integer + path: + type: string + required: + - path + type: object + type: object + type: array + type: object + quobyte: + properties: + group: + type: string + readOnly: + type: boolean + registry: + type: string + tenant: + type: string + user: + type: string + volume: + type: string + required: + - registry + - volume + type: object + rbd: + properties: + fsType: + type: string + image: + type: string + keyring: + type: string + monitors: + items: + type: string + type: array + pool: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + user: + type: string + required: + - image + - monitors + type: object + scaleIO: + properties: + fsType: + type: string + gateway: + type: string + protectionDomain: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + sslEnabled: + type: boolean + storageMode: + type: string + storagePool: + type: string + system: + type: string + volumeName: + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + optional: + type: boolean + secretName: + type: string + type: object + storageos: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + volumeName: + type: string + volumeNamespace: + type: string + type: object + vsphereVolume: + properties: + fsType: + type: string + storagePolicyID: + type: string + storagePolicyName: + type: string + volumePath: + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + type: object + type: object + sideContainersSpec: + items: + properties: + args: + items: + type: string + type: array + command: + items: + type: string + type: array + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + resourceFieldRef: + properties: + containerName: + type: string + divisor: + 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 + resource: + type: string + required: + - resource + type: object + secretKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + envFrom: + items: + properties: + configMapRef: + properties: + name: + type: string + optional: + type: boolean + type: object + prefix: + type: string + secretRef: + properties: + name: + type: string + optional: + type: boolean + type: object + type: object + type: array + image: + type: string + imagePullPolicy: + type: string + lifecycle: + properties: + postStart: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + type: object + livenessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + failureThreshold: + format: int32 + type: integer + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + name: + type: string + ports: + items: + properties: + containerPort: + format: int32 + type: integer + hostIP: + type: string + hostPort: + format: int32 + type: integer + name: + type: string + protocol: + default: TCP + type: string + required: + - containerPort + type: object + type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + failureThreshold: + format: int32 + type: integer + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + 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 + 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 + type: object + type: object + securityContext: + properties: + allowPrivilegeEscalation: + type: boolean + capabilities: + properties: + add: + items: + type: string + type: array + drop: + items: + type: string + type: array + type: object + privileged: + type: boolean + procMount: + type: string + readOnlyRootFilesystem: + type: boolean + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + startupProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + failureThreshold: + format: int32 + type: integer + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + stdin: + type: boolean + stdinOnce: + type: boolean + terminationMessagePath: + type: string + terminationMessagePolicy: + type: string + tty: + type: boolean + volumeDevices: + items: + properties: + devicePath: + type: string + name: + type: string + required: + - devicePath + - name + type: object + type: array + volumeMounts: + items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: array + workingDir: + type: string + required: + - name + type: object + type: array + slaveHA: + description: Slave high availability mechanism configuration. + properties: + slaveHAGracePeriod: + description: Time in seconds between when a node fails, and when + slave high availability mechanism starts relocating shards. If + set to 0, will not affect cluster configuration. + format: int32 + type: integer + required: + - slaveHAGracePeriod + type: object + uiAnnotations: + additionalProperties: + type: string + description: Annotations for Redis Enterprise UI service + type: object + uiServiceType: + description: Type of service used to expose Redis Enterprise UI (https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types) + enum: + - ClusterIP + - NodePort + - LoadBalancer + - ExternalName + type: string + redisOnFlashSpec: + description: Stores configurations specific to redis on flash. If provided, the cluster will be capable of + creating redis on flash databases + properties: + enabled: + type: boolean + flashStorageEngine: + type: string + enum: + - rocksdb + storageClassName: + type: string + flashDiskSize: + 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 + required: + - enabled + - flashStorageEngine + - storageClassName + type: object + upgradeSpec: + description: Specification for upgrades of Redis Enterprise + properties: + autoUpgradeRedisEnterprise: + description: Whether to upgrade Redis Enterprise automatically when + operator is upgraded + type: boolean + required: + - autoUpgradeRedisEnterprise + type: object + username: + description: Username for the admin user of Redis Enterprise + type: string + vaultCASecret: + description: K8s secret name containing Vault's CA cert - defaults to + "vault-ca-cert" + type: string + volumes: + description: additional volumes + items: + description: 'Volume represents a named volume in a pod that may be + accessed by any container in the pod. + More info: https://kubernetes.io/docs/concepts/storage/volumes' + properties: + awsElasticBlockStore: + properties: + fsType: + type: string + partition: + format: int32 + type: integer + readOnly: + type: boolean + volumeID: + type: string + required: + - volumeID + type: object + azureDisk: + properties: + cachingMode: + type: string + diskName: + type: string + diskURI: + type: string + fsType: + type: string + kind: + type: string + readOnly: + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + properties: + readOnly: + type: boolean + secretName: + type: string + shareName: + type: string + required: + - secretName + - shareName + type: object + cephfs: + properties: + monitors: + items: + type: string + type: array + path: + type: string + readOnly: + type: boolean + secretFile: + type: string + secretRef: + properties: + name: + type: string + type: object + user: + type: string + required: + - monitors + type: object + cinder: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + volumeID: + type: string + required: + - volumeID + type: object + configMap: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + csi: + properties: + driver: + type: string + fsType: + type: string + nodePublishSecretRef: + properties: + name: + type: string + type: object + readOnly: + type: boolean + volumeAttributes: + additionalProperties: + type: string + type: object + required: + - driver + type: object + downwardAPI: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + 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 + resource: + type: string + required: + - resource + type: object + required: + - path + type: object + type: array + type: object + emptyDir: + properties: + medium: + type: string + sizeLimit: + 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 + type: object + fc: + properties: + fsType: + type: string + lun: + format: int32 + type: integer + readOnly: + type: boolean + targetWWNs: + items: + type: string + type: array + wwids: + items: + type: string + type: array + type: object + flexVolume: + properties: + driver: + type: string + fsType: + type: string + options: + additionalProperties: + type: string + type: object + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + required: + - driver + type: object + flocker: + properties: + datasetName: + type: string + datasetUUID: + type: string + type: object + gcePersistentDisk: + properties: + fsType: + type: string + partition: + format: int32 + type: integer + pdName: + type: string + readOnly: + type: boolean + required: + - pdName + type: object + gitRepo: + properties: + directory: + type: string + repository: + type: string + revision: + type: string + required: + - repository + type: object + glusterfs: + properties: + endpoints: + type: string + path: + type: string + readOnly: + type: boolean + required: + - endpoints + - path + type: object + hostPath: + properties: + path: + type: string + type: + type: string + required: + - path + type: object + iscsi: + properties: + chapAuthDiscovery: + type: boolean + chapAuthSession: + type: boolean + fsType: + type: string + initiatorName: + type: string + iqn: + type: string + iscsiInterface: + type: string + lun: + format: int32 + type: integer + portals: + items: + type: string + type: array + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + targetPortal: + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + type: string + nfs: + properties: + path: + type: string + readOnly: + type: boolean + server: + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + properties: + claimName: + type: string + readOnly: + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + properties: + fsType: + type: string + pdID: + type: string + required: + - pdID + type: object + portworxVolume: + properties: + fsType: + type: string + readOnly: + type: boolean + volumeID: + type: string + required: + - volumeID + type: object + projected: + properties: + defaultMode: + format: int32 + type: integer + sources: + items: + properties: + configMap: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + downwardAPI: + properties: + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + 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 + resource: + type: string + required: + - resource + type: object + required: + - path + type: object + type: array + type: object + secret: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + serviceAccountToken: + properties: + audience: + type: string + expirationSeconds: + format: int64 + type: integer + path: + type: string + required: + - path + type: object + type: object + type: array + required: + - sources + type: object + quobyte: + properties: + group: + type: string + readOnly: + type: boolean + registry: + type: string + tenant: + type: string + user: + type: string + volume: + type: string + required: + - registry + - volume + type: object + rbd: + properties: + fsType: + type: string + image: + type: string + keyring: + type: string + monitors: + items: + type: string + type: array + pool: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + user: + type: string + required: + - image + - monitors + type: object + scaleIO: + properties: + fsType: + type: string + gateway: + type: string + protectionDomain: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + sslEnabled: + type: boolean + storageMode: + type: string + storagePool: + type: string + system: + type: string + volumeName: + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + optional: + type: boolean + secretName: + type: string + type: object + storageos: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + volumeName: + type: string + volumeNamespace: + type: string + type: object + vsphereVolume: + properties: + fsType: + type: string + storagePolicyID: + type: string + storagePolicyName: + type: string + volumePath: + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + type: object + type: object + - name: v1alpha1 + additionalPrinterColumns: + - jsonPath: .spec.nodes + name: Nodes + type: string + - jsonPath: .spec.redisEnterpriseImageSpec.versionTag + name: Version + type: string + - jsonPath: .status.state + name: State + type: string + - jsonPath: .status.specStatus + name: Spec Status + type: string + - jsonPath: .status.licenseStatus.licenseState + name: License State + type: string + - jsonPath: .status.licenseStatus.shardsLimit + name: Shards Limit + type: string + - jsonPath: .status.licenseStatus.expirationDate + name: License Expiration Date + type: string + - name: Age + type: date + jsonPath: .metadata.creationTimestamp + served: true + storage: true + subresources: + status: {} + schema: + openAPIV3Schema: + description: RedisEnterpriseCluster is the Schema for the redisenterpriseclusters + API + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + status: + type: object + properties: + specStatus: + type: string + state: + type: string + modules: + type: array + items: + type: object + properties: + name: + type: string + displayName: + type: string + versions: + type: array + items: + type: string + ocspStatus: + description: An API object that represents the cluster's OCSP status + properties: + certStatus: + description: Indicates the proxy certificate status - GOOD/REVOKED/UNKNOWN. + type: string + nextUpdate: + description: The time at or before which newer information will + be available about the status of the certificate (if available) + type: string + producedAt: + description: The time at which the OCSP responder signed this + response. + type: string + responderUrl: + description: The OCSP responder url from which this status came + from. + type: string + revocationTime: + description: The time at which the certificate was revoked or + placed on hold. + type: string + thisUpdate: + description: The most recent time at which the status being indicated + is known by the responder to have been correct. + type: string + type: object + licenseStatus: + type: object + properties: + licenseState: + type: string + activationDate: + type: string + expirationDate: + type: string + shardsLimit: + type: integer + bundledDatabaseVersions: + description: Versions of open source databases bundled by Redis Enterprise + Software - please note that in order to use a specific version it + should be supported by the ‘upgradePolicy’ - ‘major’ or ‘latest’ according + to the desired version (major/minor) + items: + properties: + dbType: + type: string + version: + type: string + required: + - dbType + - version + type: object + type: array + spec: + description: RedisEnterpriseClusterSpec defines the desired state of RedisEnterpriseCluster + properties: + activeActive: + description: Specification for ActiveActive setup + properties: + apiIngressUrl: + description: RS API URL + type: string + dbIngressSuffix: + description: DB ENDPOINT SUFFIX - will be used to set the db host. + ingress Creates a host name so it + should be unique if more than one db is created on the cluster + with the same name + type: string + ingressAnnotations: + additionalProperties: + type: string + description: Used for ingress controllers such as ha-proxy or nginx + in GKE + type: object + method: + description: Used to distinguish between different platforms implementation + enum: + - openShiftRoute + - ingress + type: string + required: + - apiIngressUrl + - dbIngressSuffix + - method + type: object + antiAffinityAdditionalTopologyKeys: + description: Additional antiAffinity terms in order to support installation + on different zones/vcenters + items: + type: string + type: array + bootstrapperImageSpec: + description: Specification for Bootstrapper container image + properties: + imagePullPolicy: + description: PullPolicy describes a policy for if/when to pull a + container image + type: string + repository: + description: Repository + type: string + versionTag: + type: string + type: object + bootstrapperResources: + description: Compute resource requirements for bootstrapper containers + 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-compute-resources-container/' + 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-compute-resources-container/' + type: object + type: object + clusterCredentialSecretName: + description: Secret Name/Path to use for Cluster Credentials. If left + blank, will use cluster name + type: string + clusterCredentialSecretRole: + description: Used only if ClusterCredentialSecretType is vault, to define + vault role to be used. If blank, defaults to "redis-enterprise-operator" + type: string + clusterCredentialSecretType: + description: Type of Secret to use for ClusterCredential, Vault, Kuberetes,... + If left blank, will default ot kubernetes secrets + enum: + - vault + - kubernetes + type: string + clusterRecovery: + description: ClusterRecovery initiates cluster recovery when set to + true. Note that this field is cleared automatically after the cluster + is recovered + type: boolean + createServiceAccount: + description: Whether to create service account + type: boolean + dataInternodeEncryption: + description: Internode encryption (INE) cluster wide policy. An optional boolean setting. + Specifies if INE should be on/off for new created REDBs. + May be overridden for specific REDB via similar setting, + please view the similar setting for REDB for more info. + type: boolean + encryptPkeys: + description: 'Private key encryption - in order to enable, first need + to mount ${ephemeralconfdir}/secrets/pem/passphrase and add the passphrase + and then set fields value to ''true'' Possible values: true/false' + type: boolean + certificates: + description: RS Cluster Certificates. + Used to modify the certificates used by the cluster. + See the "RSClusterCertificates" struct described above to see the supported certificates. + properties: + apiCertificateSecretName: + description: Secret Name/Path to use for Cluster's API Certificate. + If left blank, will use certificate provided by the cluster. + type: string + cmCertificateSecretName: + description: Secret Name/Path to use for Cluster's CM Certificate. + If left blank, will use certificate provided by the cluster. + type: string + metricsExporterCertificateSecretName: + description: Secret Name/Path to use for Cluster's Metrics Exporter Certificate. + If left blank, will use certificate provided by the cluster. + type: string + proxyCertificateSecretName: + description: Secret Name/Path to use for Cluster's Proxy Certificate. + If left blank, will use certificate provided by the cluster. + type: string + syncerCertificateSecretName: + description: Secret Name/Path to use for Cluster's Syncer Certificate. + If left blank, will use certificate provided by the cluster. + type: string + type: object + enforceIPv4: + description: Sets ENFORCE_IPV4 environment variable + type: boolean + extraLabels: + additionalProperties: + type: string + description: Labels that the user defines for their convenience + type: object + hostAliases: + items: + description: HostAlias holds the mapping between IP and hostnames + that will be injected as an entry in the pod's hosts file. + properties: + hostnames: + description: Hostnames for the above IP address. + items: + type: string + type: array + ip: + description: IP address of the host file entry. + type: string + type: object + type: array + license: + description: Redis Enterprise License + type: string + licenseSecretName: + description: K8s secret or Vault Secret Name/Path to use for Cluster + License. When left blank, the license is read from the "license" field. + Note that you can't specify non-empty values in both "license" and + "licenseSecretName", only one of these fields can be used to pass + the license string. The license needs to be stored under the key "license". + type: string + nodeSelector: + additionalProperties: + type: string + description: Selector for nodes that could fit Redis Enterprise pod + type: object + ocspConfiguration: + description: An API object that represents the cluster's OCSP configuration. + To enable OCSP, the cluster's proxy certificate should contain the + OCSP responder URL. + properties: + ocspFunctionality: + description: Whether to enable/disable OCSP mechanism for the + cluster. + type: boolean + queryFrequency: + description: Determines the interval (in seconds) in which the + control plane will poll the OCSP responder for a new status + for the server certificate. Minimum value is 60. Maximum value + is 86400. + type: integer + recoveryFrequency: + description: Determines the interval (in seconds) in which the + control plane will poll the OCSP responder for a new status + for the server certificate when the current staple is invalid. + Minimum value is 60. Maximum value is 86400. + type: integer + recoveryMaxTries: + description: Determines the maximum number for the OCSP recovery + attempts. After max number of tries passed, the control plane + will revert back to the regular frequency. Minimum value is + 1. Maximum value is 100. + type: integer + responseTimeout: + description: Determines the time interval (in seconds) for which + the request waits for a response from the OCSP responder. Minimum + value is 1. Maximum value is 60. + type: integer + type: object + nodes: + description: Number of Redis Enterprise nodes (pods) + format: int32 + type: integer + persistentSpec: + description: Specification for Redis Enterprise Cluster persistence + properties: + enabled: + description: Whether to add persistent volume to Redis Enterprise + pods + type: boolean + storageClassName: + description: Storage class for persistent volume in Redis Enterprise + pods Leave empty to use the default + type: string + volumeSize: + 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 + type: object + podAnnotations: + additionalProperties: + type: string + description: pod annotations + type: object + podAntiAffinity: + description: 'Override for the default anti-affinity rules of the Redis + Enterprise pods. + More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#an-example-of-a-pod-that-uses-pod-affinity' + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + type: object + podSecurityPolicyName: + description: Name of pod security policy to use on pods See https://kubernetes.io/docs/concepts/policy/pod-security-policy/ + type: string + podStartingPolicy: + description: Mitigation setting for STS pods stuck in "ContainerCreating" + properties: + enabled: + description: Whether to detect and attempt to mitigate pod startup + issues + type: boolean + startingThresholdSeconds: + description: Time in seconds to wait for a pod to be stuck while + starting up before action is taken. If set to 0, will be treated + as if disabled. + format: int32 + type: integer + required: + - enabled + - startingThresholdSeconds + type: object + podTolerations: + description: 'Tolerations that are added to all managed pods. More + information: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/' + items: + properties: + effect: + type: string + key: + type: string + operator: + type: string + tolerationSeconds: + format: int64 + type: integer + value: + type: string + type: object + type: array + priorityClassName: + description: Adds the priority class to pods managed by the operator + type: string + pullSecrets: + description: 'PullSecrets is an optional list of references to secrets + in the same namespace to use for pulling any of the images. If specified, + these secrets will be passed to individual puller implementations + for them to use. More info: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/' + items: + properties: + name: + description: 'Secret name' + type: string + type: object + type: array + rackAwarenessNodeLabel: + description: Node label that specifies rack ID - if specified, will + create rack aware cluster. Rack awareness requires node label must + exist on all nodes. Additionally, operator needs a special cluster + role with permission to list nodes. + type: string + redisEnterpriseAdditionalPodSpecAttributes: + description: ADVANCED USAGE USE AT YOUR OWN RISK - specify pod attributes + that are required for the statefulset - Redis Enterprise pods. Pod + attributes managed by the operator might override these settings. + Also make sure the attributes are supported by the K8s version running + on the cluster - the operator does not validate that. + properties: + activeDeadlineSeconds: + format: int64 + type: integer + affinity: + properties: + nodeAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + preference: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + properties: + nodeSelectorTerms: + items: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + type: array + required: + - nodeSelectorTerms + type: object + type: object + podAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + automountServiceAccountToken: + type: boolean + + dnsConfig: + properties: + nameservers: + items: + type: string + type: array + options: + items: + properties: + name: + type: string + value: + type: string + type: object + type: array + searches: + items: + type: string + type: array + type: object + dnsPolicy: + type: string + enableServiceLinks: + type: boolean + ephemeralContainers: + items: + properties: + args: + items: + type: string + type: array + command: + items: + type: string + type: array + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + resourceFieldRef: + properties: + containerName: + type: string + divisor: + 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 + resource: + type: string + required: + - resource + type: object + secretKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + envFrom: + items: + properties: + configMapRef: + properties: + name: + type: string + optional: + type: boolean + type: object + prefix: + type: string + secretRef: + properties: + name: + type: string + optional: + type: boolean + type: object + type: object + type: array + image: + type: string + imagePullPolicy: + type: string + lifecycle: + properties: + postStart: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + type: object + livenessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + failureThreshold: + format: int32 + type: integer + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + name: + type: string + ports: + items: + properties: + containerPort: + format: int32 + type: integer + hostIP: + type: string + hostPort: + format: int32 + type: integer + name: + type: string + protocol: + default: TCP + type: string + required: + - containerPort + type: object + type: array + readinessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + failureThreshold: + format: int32 + type: integer + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + 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 + 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 + type: object + type: object + securityContext: + properties: + allowPrivilegeEscalation: + type: boolean + capabilities: + properties: + add: + items: + type: string + type: array + drop: + items: + type: string + type: array + type: object + privileged: + type: boolean + procMount: + type: string + readOnlyRootFilesystem: + type: boolean + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + startupProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + failureThreshold: + format: int32 + type: integer + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + stdin: + type: boolean + stdinOnce: + type: boolean + targetContainerName: + type: string + terminationMessagePath: + type: string + terminationMessagePolicy: + type: string + tty: + type: boolean + volumeDevices: + items: + properties: + devicePath: + type: string + name: + type: string + required: + - devicePath + - name + type: object + type: array + volumeMounts: + items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: array + workingDir: + type: string + required: + - name + type: object + type: array + hostAliases: + items: + properties: + hostnames: + items: + type: string + type: array + ip: + type: string + type: object + type: array + hostIPC: + type: boolean + hostNetwork: + type: boolean + hostPID: + type: boolean + hostname: + type: string + imagePullSecrets: + items: + properties: + name: + type: string + type: object + type: array + initContainers: + items: + properties: + args: + items: + type: string + type: array + command: + items: + type: string + type: array + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + resourceFieldRef: + properties: + containerName: + type: string + divisor: + 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 + resource: + type: string + required: + - resource + type: object + secretKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + envFrom: + items: + properties: + configMapRef: + properties: + name: + type: string + optional: + type: boolean + type: object + prefix: + type: string + secretRef: + properties: + name: + type: string + optional: + type: boolean + type: object + type: object + type: array + image: + type: string + imagePullPolicy: + type: string + lifecycle: + properties: + postStart: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + type: object + livenessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + failureThreshold: + format: int32 + type: integer + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + name: + type: string + ports: + items: + properties: + containerPort: + format: int32 + type: integer + hostIP: + type: string + hostPort: + format: int32 + type: integer + name: + type: string + protocol: + default: TCP + type: string + required: + - containerPort + type: object + type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + failureThreshold: + format: int32 + type: integer + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + 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 + 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 + type: object + type: object + securityContext: + properties: + allowPrivilegeEscalation: + type: boolean + capabilities: + properties: + add: + items: + type: string + type: array + drop: + items: + type: string + type: array + type: object + privileged: + type: boolean + procMount: + type: string + readOnlyRootFilesystem: + type: boolean + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + startupProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + failureThreshold: + format: int32 + type: integer + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + stdin: + type: boolean + stdinOnce: + type: boolean + terminationMessagePath: + type: string + terminationMessagePolicy: + type: string + tty: + type: boolean + volumeDevices: + items: + properties: + devicePath: + type: string + name: + type: string + required: + - devicePath + - name + type: object + type: array + volumeMounts: + items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: array + workingDir: + type: string + required: + - name + type: object + type: array + nodeName: + type: string + nodeSelector: + additionalProperties: + type: string + type: object + x-kubernetes-map-type: atomic + overhead: + 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 + type: object + preemptionPolicy: + type: string + priority: + format: int32 + type: integer + priorityClassName: + type: string + readinessGates: + items: + properties: + conditionType: + type: string + required: + - conditionType + type: object + type: array + restartPolicy: + type: string + runtimeClassName: + type: string + schedulerName: + type: string + securityContext: + properties: + fsGroup: + format: int64 + type: integer + fsGroupChangePolicy: + type: string + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + supplementalGroups: + items: + format: int64 + type: integer + type: array + sysctls: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + serviceAccount: + type: string + serviceAccountName: + type: string + setHostnameAsFQDN: + type: boolean + shareProcessNamespace: + type: boolean + subdomain: + type: string + terminationGracePeriodSeconds: + format: int64 + type: integer + tolerations: + items: + properties: + effect: + type: string + key: + type: string + operator: + type: string + tolerationSeconds: + format: int64 + type: integer + value: + type: string + type: object + type: array + topologySpreadConstraints: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + maxSkew: + format: int32 + type: integer + topologyKey: + type: string + whenUnsatisfiable: + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + x-kubernetes-list-map-keys: + - topologyKey + - whenUnsatisfiable + x-kubernetes-list-type: map + volumes: + items: + properties: + awsElasticBlockStore: + properties: + fsType: + type: string + partition: + format: int32 + type: integer + readOnly: + type: boolean + volumeID: + type: string + required: + - volumeID + type: object + azureDisk: + properties: + cachingMode: + type: string + diskName: + type: string + diskURI: + type: string + fsType: + type: string + kind: + type: string + readOnly: + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + properties: + readOnly: + type: boolean + secretName: + type: string + shareName: + type: string + required: + - secretName + - shareName + type: object + cephfs: + properties: + monitors: + items: + type: string + type: array + path: + type: string + readOnly: + type: boolean + secretFile: + type: string + secretRef: + properties: + name: + type: string + type: object + user: + type: string + required: + - monitors + type: object + cinder: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + volumeID: + type: string + required: + - volumeID + type: object + configMap: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + csi: + properties: + driver: + type: string + fsType: + type: string + nodePublishSecretRef: + properties: + name: + type: string + type: object + readOnly: + type: boolean + volumeAttributes: + additionalProperties: + type: string + type: object + required: + - driver + type: object + downwardAPI: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + 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 + resource: + type: string + required: + - resource + type: object + required: + - path + type: object + type: array + type: object + emptyDir: + properties: + medium: + type: string + sizeLimit: + 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 + type: object + ephemeral: + properties: + volumeClaimTemplate: + properties: + metadata: + type: object + spec: + properties: + accessModes: + items: + type: string + type: array + dataSource: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + required: + - kind + - name + type: object + dataSourceRef: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + required: + - kind + - name + type: object + 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 + 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 + type: object + type: object + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + storageClassName: + type: string + volumeMode: + type: string + volumeName: + type: string + type: object + required: + - spec + type: object + type: object + fc: + properties: + fsType: + type: string + lun: + format: int32 + type: integer + readOnly: + type: boolean + targetWWNs: + items: + type: string + type: array + wwids: + items: + type: string + type: array + type: object + flexVolume: + properties: + driver: + type: string + fsType: + type: string + options: + additionalProperties: + type: string + type: object + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + required: + - driver + type: object + flocker: + properties: + datasetName: + type: string + datasetUUID: + type: string + type: object + gcePersistentDisk: + properties: + fsType: + type: string + partition: + format: int32 + type: integer + pdName: + type: string + readOnly: + type: boolean + required: + - pdName + type: object + gitRepo: + properties: + directory: + type: string + repository: + type: string + revision: + type: string + required: + - repository + type: object + glusterfs: + properties: + endpoints: + type: string + path: + type: string + readOnly: + type: boolean + required: + - endpoints + - path + type: object + hostPath: + properties: + path: + type: string + type: + type: string + required: + - path + type: object + iscsi: + properties: + chapAuthDiscovery: + type: boolean + chapAuthSession: + type: boolean + fsType: + type: string + initiatorName: + type: string + iqn: + type: string + iscsiInterface: + type: string + lun: + format: int32 + type: integer + portals: + items: + type: string + type: array + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + targetPortal: + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + type: string + nfs: + properties: + path: + type: string + readOnly: + type: boolean + server: + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + properties: + claimName: + type: string + readOnly: + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + properties: + fsType: + type: string + pdID: + type: string + required: + - pdID + type: object + portworxVolume: + properties: + fsType: + type: string + readOnly: + type: boolean + volumeID: + type: string + required: + - volumeID + type: object + projected: + properties: + defaultMode: + format: int32 + type: integer + sources: + items: + properties: + configMap: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + downwardAPI: + properties: + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + 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 + resource: + type: string + required: + - resource + type: object + required: + - path + type: object + type: array + type: object + secret: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + serviceAccountToken: + properties: + audience: + type: string + expirationSeconds: + format: int64 + type: integer + path: + type: string + required: + - path + type: object + type: object + type: array + type: object + quobyte: + properties: + group: + type: string + readOnly: + type: boolean + registry: + type: string + tenant: + type: string + user: + type: string + volume: + type: string + required: + - registry + - volume + type: object + rbd: + properties: + fsType: + type: string + image: + type: string + keyring: + type: string + monitors: + items: + type: string + type: array + pool: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + user: + type: string + required: + - image + - monitors + type: object + scaleIO: + properties: + fsType: + type: string + gateway: + type: string + protectionDomain: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + sslEnabled: + type: boolean + storageMode: + type: string + storagePool: + type: string + system: + type: string + volumeName: + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + optional: + type: boolean + secretName: + type: string + type: object + storageos: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + volumeName: + type: string + volumeNamespace: + type: string + type: object + vsphereVolume: + properties: + fsType: + type: string + storagePolicyID: + type: string + storagePolicyName: + type: string + volumePath: + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + type: object + redisEnterpriseImageSpec: + description: Specification for Redis Enterprise container image + properties: + imagePullPolicy: + description: PullPolicy describes a policy for if/when to pull a + container image + type: string + repository: + description: Repository + type: string + versionTag: + type: string + type: object + redisEnterpriseNodeResources: + description: Compute resource requirements for Redis Enterprise containers + 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-compute-resources-container/' + 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-compute-resources-container/' + type: object + type: object + redisEnterpriseServicesConfiguration: + description: RS Cluster optional services settings + properties: + cmServer: + properties: + operatingMode: + description: Whether to enable/disable the CM server + enum: + - enabled + - disabled + type: string + required: + - operatingMode + type: object + crdbCoordinator: + properties: + operatingMode: + description: Whether to enable/disable the crdb coordinator + process + enum: + - enabled + - disabled + type: string + required: + - operatingMode + type: object + crdbWorker: + properties: + operatingMode: + description: Whether to enable/disable the crdb worker processes + enum: + - enabled + - disabled + type: string + required: + - operatingMode + type: object + mdnsServer: + properties: + operatingMode: + description: Whether to enable/disable the Multicast DNS server + enum: + - enabled + - disabled + type: string + required: + - operatingMode + type: object + pdnsServer: + properties: + operatingMode: + description: Whether to enable/disable the pdns server + enum: + - enabled + - disabled + type: string + required: + - operatingMode + type: object + saslauthd: + properties: + operatingMode: + description: Whether to enable/disable the saslauthd service + enum: + - enabled + - disabled + type: string + required: + - operatingMode + type: object + statsArchiver: + properties: + operatingMode: + description: Whether to enable/disable the stats archiver service + enum: + - enabled + - disabled + type: string + required: + - operatingMode + type: object + type: object + redisEnterpriseServicesRiggerImageSpec: + description: Specification for Services Rigger container image + properties: + imagePullPolicy: + description: PullPolicy describes a policy for if/when to pull a + container image + type: string + repository: + description: Repository + type: string + versionTag: + type: string + type: object + redisEnterpriseServicesRiggerResources: + description: Compute resource requirements for Services Rigger pod + 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-compute-resources-container/' + 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-compute-resources-container/' + type: object + type: object + redisEnterpriseTerminationGracePeriodSeconds: + description: The TerminationGracePeriodSeconds value for the (STS created) REC pods + format: int64 + type: integer + redisEnterpriseVolumeMounts: + description: 'additional volume mounts within the redis enterprise containers. + More info: https://kubernetes.io/docs/concepts/storage/volumes/' + items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: array + redisUpgradePolicy: + description: 'Redis upgrade policy to be set on the Redis Enterprise + Cluster. Possible values: major/latest This value is used by the cluster + to choose the Redis version of the database when an upgrade is performed. + The Redis Enterprise Cluster includes multiple versions of OSS Redis + that can be used for databases.' + enum: + - major + - latest + type: string + serviceAccountName: + description: Name of the service account to use + type: string + servicesRiggerSpec: + description: Specification for service rigger + properties: + databaseServiceType: + description: Service types for access to databases. should be a + comma separated list. The possible values are cluster_ip, headless + and load_balancer. + type: string + extraEnvVars: + items: + description: 'EnvVar represents an environment variable present + in a Container. + More info: https://kubernetes.io/docs/tasks/inject-data-application/environment-variable-expose-pod-information/' + properties: + name: + description: Name of the environment variable. + type: string + value: + 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 + type: string + optional: + description: Specify whether the ConfigMap or its + key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: Selects a field of the pod + 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 + resourceFieldRef: + description: 'Selects a resource of the container: only + resources limits and requests 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 + 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 + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + serviceNaming: + enum: + - bdb_name + - redis-port + type: string + servicesRiggerAdditionalPodSpecAttributes: + description: ADVANCED USAGE USE AT YOUR OWN RISK - specify pod attributes + that are required for the rigger deployment pod. Pod attributes + managed by the operator might override these settings (Containers, + serviceAccountName, podTolerations, ImagePullSecrets, nodeSelector, + PriorityClassName, PodSecurityContext). Also make sure the attributes are supported + by the K8s version running on the cluster - the operator does + not validate that. + properties: + activeDeadlineSeconds: + format: int64 + type: integer + affinity: + properties: + nodeAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + preference: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + properties: + nodeSelectorTerms: + items: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + type: array + required: + - nodeSelectorTerms + type: object + type: object + podAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + automountServiceAccountToken: + type: boolean + dnsConfig: + properties: + nameservers: + items: + type: string + type: array + options: + items: + properties: + name: + type: string + value: + type: string + type: object + type: array + searches: + items: + type: string + type: array + type: object + dnsPolicy: + type: string + enableServiceLinks: + type: boolean + ephemeralContainers: + items: + properties: + args: + items: + type: string + type: array + command: + items: + type: string + type: array + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + resourceFieldRef: + properties: + containerName: + type: string + divisor: + 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 + resource: + type: string + required: + - resource + type: object + secretKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + envFrom: + items: + properties: + configMapRef: + properties: + name: + type: string + optional: + type: boolean + type: object + prefix: + type: string + secretRef: + properties: + name: + type: string + optional: + type: boolean + type: object + type: object + type: array + image: + type: string + imagePullPolicy: + type: string + lifecycle: + properties: + postStart: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + type: object + livenessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + failureThreshold: + format: int32 + type: integer + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + name: + type: string + ports: + items: + properties: + containerPort: + format: int32 + type: integer + hostIP: + type: string + hostPort: + format: int32 + type: integer + name: + type: string + protocol: + default: TCP + type: string + required: + - containerPort + type: object + type: array + readinessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + failureThreshold: + format: int32 + type: integer + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + 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 + 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 + type: object + type: object + securityContext: + properties: + allowPrivilegeEscalation: + type: boolean + capabilities: + properties: + add: + items: + type: string + type: array + drop: + items: + type: string + type: array + type: object + privileged: + type: boolean + procMount: + type: string + readOnlyRootFilesystem: + type: boolean + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + startupProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + failureThreshold: + format: int32 + type: integer + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + stdin: + type: boolean + stdinOnce: + type: boolean + targetContainerName: + type: string + terminationMessagePath: + type: string + terminationMessagePolicy: + type: string + tty: + type: boolean + volumeDevices: + items: + properties: + devicePath: + type: string + name: + type: string + required: + - devicePath + - name + type: object + type: array + volumeMounts: + items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: array + workingDir: + type: string + required: + - name + type: object + type: array + hostAliases: + items: + properties: + hostnames: + items: + type: string + type: array + ip: + type: string + type: object + type: array + hostIPC: + type: boolean + hostNetwork: + type: boolean + hostPID: + type: boolean + hostname: + type: string + imagePullSecrets: + items: + properties: + name: + type: string + type: object + type: array + initContainers: + items: + properties: + args: + items: + type: string + type: array + command: + items: + type: string + type: array + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + resourceFieldRef: + properties: + containerName: + type: string + divisor: + 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 + resource: + type: string + required: + - resource + type: object + secretKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + envFrom: + items: + properties: + configMapRef: + properties: + name: + type: string + optional: + type: boolean + type: object + prefix: + type: string + secretRef: + properties: + name: + type: string + optional: + type: boolean + type: object + type: object + type: array + image: + type: string + imagePullPolicy: + type: string + lifecycle: + properties: + postStart: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + type: object + livenessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + failureThreshold: + format: int32 + type: integer + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + name: + type: string + ports: + items: + properties: + containerPort: + format: int32 + type: integer + hostIP: + type: string + hostPort: + format: int32 + type: integer + name: + type: string + protocol: + default: TCP + type: string + required: + - containerPort + type: object + type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + failureThreshold: + format: int32 + type: integer + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + 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 + 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 + type: object + type: object + securityContext: + properties: + allowPrivilegeEscalation: + type: boolean + capabilities: + properties: + add: + items: + type: string + type: array + drop: + items: + type: string + type: array + type: object + privileged: + type: boolean + procMount: + type: string + readOnlyRootFilesystem: + type: boolean + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + startupProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + failureThreshold: + format: int32 + type: integer + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + stdin: + type: boolean + stdinOnce: + type: boolean + terminationMessagePath: + type: string + terminationMessagePolicy: + type: string + tty: + type: boolean + volumeDevices: + items: + properties: + devicePath: + type: string + name: + type: string + required: + - devicePath + - name + type: object + type: array + volumeMounts: + items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: array + workingDir: + type: string + required: + - name + type: object + type: array + nodeName: + type: string + nodeSelector: + additionalProperties: + type: string + type: object + x-kubernetes-map-type: atomic + overhead: + 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 + type: object + preemptionPolicy: + type: string + priority: + format: int32 + type: integer + priorityClassName: + type: string + readinessGates: + items: + properties: + conditionType: + type: string + required: + - conditionType + type: object + type: array + restartPolicy: + type: string + runtimeClassName: + type: string + schedulerName: + type: string + securityContext: + properties: + fsGroup: + format: int64 + type: integer + fsGroupChangePolicy: + type: string + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + supplementalGroups: + items: + format: int64 + type: integer + type: array + sysctls: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + serviceAccount: + type: string + serviceAccountName: + type: string + setHostnameAsFQDN: + type: boolean + shareProcessNamespace: + type: boolean + subdomain: + type: string + terminationGracePeriodSeconds: + format: int64 + type: integer + tolerations: + items: + properties: + effect: + type: string + key: + type: string + operator: + type: string + tolerationSeconds: + format: int64 + type: integer + value: + type: string + type: object + type: array + topologySpreadConstraints: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + maxSkew: + format: int32 + type: integer + topologyKey: + type: string + whenUnsatisfiable: + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + x-kubernetes-list-map-keys: + - topologyKey + - whenUnsatisfiable + x-kubernetes-list-type: map + volumes: + items: + properties: + awsElasticBlockStore: + properties: + fsType: + type: string + partition: + format: int32 + type: integer + readOnly: + type: boolean + volumeID: + type: string + required: + - volumeID + type: object + azureDisk: + properties: + cachingMode: + type: string + diskName: + type: string + diskURI: + type: string + fsType: + type: string + kind: + type: string + readOnly: + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + properties: + readOnly: + type: boolean + secretName: + type: string + shareName: + type: string + required: + - secretName + - shareName + type: object + cephfs: + properties: + monitors: + items: + type: string + type: array + path: + type: string + readOnly: + type: boolean + secretFile: + type: string + secretRef: + properties: + name: + type: string + type: object + user: + type: string + required: + - monitors + type: object + cinder: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + volumeID: + type: string + required: + - volumeID + type: object + configMap: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + csi: + properties: + driver: + type: string + fsType: + type: string + nodePublishSecretRef: + properties: + name: + type: string + type: object + readOnly: + type: boolean + volumeAttributes: + additionalProperties: + type: string + type: object + required: + - driver + type: object + downwardAPI: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + 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 + resource: + type: string + required: + - resource + type: object + required: + - path + type: object + type: array + type: object + emptyDir: + properties: + medium: + type: string + sizeLimit: + 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 + type: object + ephemeral: + properties: + volumeClaimTemplate: + properties: + metadata: + type: object + spec: + properties: + accessModes: + items: + type: string + type: array + dataSource: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + required: + - kind + - name + type: object + dataSourceRef: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + required: + - kind + - name + type: object + 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 + 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 + type: object + type: object + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + storageClassName: + type: string + volumeMode: + type: string + volumeName: + type: string + type: object + required: + - spec + type: object + type: object + fc: + properties: + fsType: + type: string + lun: + format: int32 + type: integer + readOnly: + type: boolean + targetWWNs: + items: + type: string + type: array + wwids: + items: + type: string + type: array + type: object + flexVolume: + properties: + driver: + type: string + fsType: + type: string + options: + additionalProperties: + type: string + type: object + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + required: + - driver + type: object + flocker: + properties: + datasetName: + type: string + datasetUUID: + type: string + type: object + gcePersistentDisk: + properties: + fsType: + type: string + partition: + format: int32 + type: integer + pdName: + type: string + readOnly: + type: boolean + required: + - pdName + type: object + gitRepo: + properties: + directory: + type: string + repository: + type: string + revision: + type: string + required: + - repository + type: object + glusterfs: + properties: + endpoints: + type: string + path: + type: string + readOnly: + type: boolean + required: + - endpoints + - path + type: object + hostPath: + properties: + path: + type: string + type: + type: string + required: + - path + type: object + iscsi: + properties: + chapAuthDiscovery: + type: boolean + chapAuthSession: + type: boolean + fsType: + type: string + initiatorName: + type: string + iqn: + type: string + iscsiInterface: + type: string + lun: + format: int32 + type: integer + portals: + items: + type: string + type: array + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + targetPortal: + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + type: string + nfs: + properties: + path: + type: string + readOnly: + type: boolean + server: + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + properties: + claimName: + type: string + readOnly: + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + properties: + fsType: + type: string + pdID: + type: string + required: + - pdID + type: object + portworxVolume: + properties: + fsType: + type: string + readOnly: + type: boolean + volumeID: + type: string + required: + - volumeID + type: object + projected: + properties: + defaultMode: + format: int32 + type: integer + sources: + items: + properties: + configMap: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + downwardAPI: + properties: + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + 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 + resource: + type: string + required: + - resource + type: object + required: + - path + type: object + type: array + type: object + secret: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + serviceAccountToken: + properties: + audience: + type: string + expirationSeconds: + format: int64 + type: integer + path: + type: string + required: + - path + type: object + type: object + type: array + type: object + quobyte: + properties: + group: + type: string + readOnly: + type: boolean + registry: + type: string + tenant: + type: string + user: + type: string + volume: + type: string + required: + - registry + - volume + type: object + rbd: + properties: + fsType: + type: string + image: + type: string + keyring: + type: string + monitors: + items: + type: string + type: array + pool: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + user: + type: string + required: + - image + - monitors + type: object + scaleIO: + properties: + fsType: + type: string + gateway: + type: string + protectionDomain: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + sslEnabled: + type: boolean + storageMode: + type: string + storagePool: + type: string + system: + type: string + volumeName: + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + optional: + type: boolean + secretName: + type: string + type: object + storageos: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + volumeName: + type: string + volumeNamespace: + type: string + type: object + vsphereVolume: + properties: + fsType: + type: string + storagePolicyID: + type: string + storagePolicyName: + type: string + volumePath: + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array type: object - secret: - properties: - defaultMode: - format: int32 - type: integer + type: object + sideContainersSpec: + items: + properties: + args: + items: + type: string + type: array + command: + items: + type: string + type: array + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + resourceFieldRef: + properties: + containerName: + type: string + divisor: + 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 + resource: + type: string + required: + - resource + type: object + secretKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + envFrom: + items: + properties: + configMapRef: + properties: + name: + type: string + optional: + type: boolean + type: object + prefix: + type: string + secretRef: + properties: + name: + type: string + optional: + type: boolean + type: object + type: object + type: array + image: + type: string + imagePullPolicy: + type: string + lifecycle: + properties: + postStart: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + type: object + livenessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + failureThreshold: + format: int32 + type: integer + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + name: + type: string + ports: + items: + properties: + containerPort: + format: int32 + type: integer + hostIP: + type: string + hostPort: + format: int32 + type: integer + name: + type: string + protocol: + default: TCP + type: string + required: + - containerPort + type: object + type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + failureThreshold: + format: int32 + type: integer + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + 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 + 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 + type: object + type: object + securityContext: + properties: + allowPrivilegeEscalation: + type: boolean + capabilities: + properties: + add: + items: + type: string + type: array + drop: + items: + type: string + type: array + type: object + privileged: + type: boolean + procMount: + type: string + readOnlyRootFilesystem: + type: boolean + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + startupProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + failureThreshold: + format: int32 + type: integer + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + stdin: + type: boolean + stdinOnce: + type: boolean + terminationMessagePath: + type: string + terminationMessagePolicy: + type: string + tty: + type: boolean + volumeDevices: + items: + properties: + devicePath: + type: string + name: + type: string + required: + - devicePath + - name + type: object + type: array + volumeMounts: items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: array + workingDir: + type: string + required: + - name + type: object + type: array + slaveHA: + description: Slave high availability mechanism configuration. + properties: + slaveHAGracePeriod: + description: Time in seconds between when a node fails, and when + slave high availability mechanism starts relocating shards. If + set to 0, will not affect cluster configuration. + format: int32 + type: integer + required: + - slaveHAGracePeriod + type: object + uiAnnotations: + additionalProperties: + type: string + description: Annotations for Redis Enterprise UI service + type: object + uiServiceType: + description: Type of service used to expose Redis Enterprise UI (https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types) + enum: + - ClusterIP + - NodePort + - LoadBalancer + - ExternalName + type: string + redisOnFlashSpec: + description: Stores configurations specific to redis on flash. If provided, the cluster will be capable of + creating redis on flash databases + properties: + enabled: + type: boolean + flashStorageEngine: + type: string + enum: + - rocksdb + storageClassName: + type: string + flashDiskSize: + 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 + required: + - enabled + - flashStorageEngine + - storageClassName + type: object + upgradeSpec: + description: Specification for upgrades of Redis Enterprise + properties: + autoUpgradeRedisEnterprise: + description: Whether to upgrade Redis Enterprise automatically when + operator is upgraded + type: boolean + required: + - autoUpgradeRedisEnterprise + type: object + username: + description: Username for the admin user of Redis Enterprise + type: string + vaultCASecret: + description: K8s secret name containing Vault's CA cert - defaults to + "vault-ca-cert" + type: string + volumes: + description: additional volumes + items: + description: 'Volume represents a named volume in a pod that may be + accessed by any container in the pod. + More info: https://kubernetes.io/docs/concepts/storage/volumes' + properties: + awsElasticBlockStore: + properties: + fsType: + type: string + partition: + format: int32 + type: integer + readOnly: + type: boolean + volumeID: + type: string + required: + - volumeID + type: object + azureDisk: + properties: + cachingMode: + type: string + diskName: + type: string + diskURI: + type: string + fsType: + type: string + kind: + type: string + readOnly: + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + properties: + readOnly: + type: boolean + secretName: + type: string + shareName: + type: string + required: + - secretName + - shareName + type: object + cephfs: + properties: + monitors: + items: + type: string + type: array + path: + type: string + readOnly: + type: boolean + secretFile: + type: string + secretRef: + properties: + name: + type: string + type: object + user: + type: string + required: + - monitors + type: object + cinder: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + volumeID: + type: string + required: + - volumeID + type: object + configMap: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + csi: + properties: + driver: + type: string + fsType: + type: string + nodePublishSecretRef: + properties: + name: + type: string + type: object + readOnly: + type: boolean + volumeAttributes: + additionalProperties: + type: string + type: object + required: + - driver + type: object + downwardAPI: + properties: + defaultMode: + format: int32 + type: integer items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + 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 + resource: + type: string + required: + - resource + type: object + required: + - path + type: object + type: array + type: object + emptyDir: + properties: + medium: + type: string + sizeLimit: + 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 + type: object + fc: + properties: + fsType: + type: string + lun: + format: int32 + type: integer + readOnly: + type: boolean + targetWWNs: + items: + type: string + type: array + wwids: + items: + type: string + type: array + type: object + flexVolume: + properties: + driver: + type: string + fsType: + type: string + options: + additionalProperties: + type: string + type: object + readOnly: + type: boolean + secretRef: properties: - key: + name: type: string - mode: - format: int32 - type: integer - path: + type: object + required: + - driver + type: object + flocker: + properties: + datasetName: + type: string + datasetUUID: + type: string + type: object + gcePersistentDisk: + properties: + fsType: + type: string + partition: + format: int32 + type: integer + pdName: + type: string + readOnly: + type: boolean + required: + - pdName + type: object + gitRepo: + properties: + directory: + type: string + repository: + type: string + revision: + type: string + required: + - repository + type: object + glusterfs: + properties: + endpoints: + type: string + path: + type: string + readOnly: + type: boolean + required: + - endpoints + - path + type: object + hostPath: + properties: + path: + type: string + type: + type: string + required: + - path + type: object + iscsi: + properties: + chapAuthDiscovery: + type: boolean + chapAuthSession: + type: boolean + fsType: + type: string + initiatorName: + type: string + iqn: + type: string + iscsiInterface: + type: string + lun: + format: int32 + type: integer + portals: + items: + type: string + type: array + readOnly: + type: boolean + secretRef: + properties: + name: type: string - required: - - key - - path type: object - type: array - optional: - type: boolean - secretName: - type: string - type: object - storageos: - properties: - fsType: - type: string - readOnly: - type: boolean - secretRef: - properties: - name: + targetPortal: + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + type: string + nfs: + properties: + path: + type: string + readOnly: + type: boolean + server: + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + properties: + claimName: + type: string + readOnly: + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + properties: + fsType: + type: string + pdID: + type: string + required: + - pdID + type: object + portworxVolume: + properties: + fsType: + type: string + readOnly: + type: boolean + volumeID: + type: string + required: + - volumeID + type: object + projected: + properties: + defaultMode: + format: int32 + type: integer + sources: + items: + properties: + configMap: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + downwardAPI: + properties: + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + 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 + resource: + type: string + required: + - resource + type: object + required: + - path + type: object + type: array + type: object + secret: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + serviceAccountToken: + properties: + audience: + type: string + expirationSeconds: + format: int64 + type: integer + path: + type: string + required: + - path + type: object + type: object + type: array + required: + - sources + type: object + quobyte: + properties: + group: + type: string + readOnly: + type: boolean + registry: + type: string + tenant: + type: string + user: + type: string + volume: + type: string + required: + - registry + - volume + type: object + rbd: + properties: + fsType: + type: string + image: + type: string + keyring: + type: string + monitors: + items: type: string - type: object - volumeName: - type: string - volumeNamespace: - type: string - type: object - vsphereVolume: - properties: - fsType: - type: string - storagePolicyID: - type: string - storagePolicyName: - type: string - volumePath: - type: string - required: - - volumePath - type: object - required: - - name - type: object - type: array - type: object - type: object + type: array + pool: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + user: + type: string + required: + - image + - monitors + type: object + scaleIO: + properties: + fsType: + type: string + gateway: + type: string + protectionDomain: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + sslEnabled: + type: boolean + storageMode: + type: string + storagePool: + type: string + system: + type: string + volumeName: + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + optional: + type: boolean + secretName: + type: string + type: object + storageos: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + volumeName: + type: string + volumeNamespace: + type: string + type: object + vsphereVolume: + properties: + fsType: + type: string + storagePolicyID: + type: string + storagePolicyName: + type: string + volumePath: + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + type: object + type: object diff --git a/chart/redis-operator/files/crd/redb_crd.yaml b/chart/redis-operator/files/crd/redb_crd.yaml index fd870b1..cef144d 100644 --- a/chart/redis-operator/files/crd/redb_crd.yaml +++ b/chart/redis-operator/files/crd/redb_crd.yaml @@ -1,30 +1,8 @@ -apiVersion: apiextensions.k8s.io/v1beta1 +apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: redisenterprisedatabases.app.redislabs.com spec: - additionalPrinterColumns: - - JSONPath: .status.version - name: Version - type: string - - JSONPath: .status.internalEndpoints[*].port - name: Port - type: string - - JSONPath: .status.redisEnterpriseCluster - name: Cluster - type: string - - JSONPath: .status.shardStatuses.active - name: Shards - type: string - - JSONPath: .status.status - name: Status - type: string - - JSONPath: .status.specStatus - name: Spec Status - type: string - - JSONPath: .metadata.creationTimestamp - name: Age - type: date group: app.redislabs.com names: kind: RedisEnterpriseDatabase @@ -34,544 +12,636 @@ spec: shortNames: - redb scope: Namespaced - subresources: - status: {} - version: v1alpha1 + preserveUnknownFields: false versions: - name: v1alpha1 served: true storage: true - validation: - openAPIV3Schema: - description: RedisEnterpriseDatabase is the Schema for the redisenterprisedatabases - API - properties: - apiVersion: - type: string - kind: - type: string - metadata: - type: object - spec: - description: RedisEnterpriseDatabaseSpec defines the desired state of RedisEnterpriseDatabase - properties: - alertSettings: - description: Settings for database alerts - properties: - bdb_backup_delayed: - description: "Periodic backup has been delayed for longer than specified threshold value [minutes]. - -Note threshold is commented (allow string/int/float and support backwards compatibility) but is required" - properties: - enabled: - description: Alert enabled or disabled - type: boolean -# threshold: -# description: Threshold for alert going on/off -# type: string - x-kubernetes-preserve-unknown-fields: true - bdb_crdt_src_high_syncer_lag: - description: "Active-active source - sync lag is higher than specified threshold value [seconds] - -Note threshold is commented (allow string/int/float and support backwards compatibility) but is required" - properties: - enabled: - description: Alert enabled or disabled - type: boolean -# threshold: -# description: Threshold for alert going on/off -# type: string - x-kubernetes-preserve-unknown-fields: true - bdb_crdt_src_syncer_connection_error: - description: "Active-active source - sync has connection error while trying to connect replica source - -Note threshold is commented (allow string/int/float and support backwards compatibility) but is required" - properties: - enabled: - description: Alert enabled or disabled - type: boolean -# threshold: -# description: Threshold for alert going on/off -# type: string - x-kubernetes-preserve-unknown-fields: true - bdb_crdt_src_syncer_general_error: - description: "Active-active source - sync encountered in general error - -Note threshold is commented (allow string/int/float and support backwards compatibility) but is required" - properties: - enabled: - description: Alert enabled or disabled - type: boolean -# threshold: -# description: Threshold for alert going on/off -# type: string - x-kubernetes-preserve-unknown-fields: true - bdb_high_latency: - description: "Latency is higher than specified threshold value [micro-sec] - -Note threshold is commented (allow string/int/float and support backwards compatibility) but is required" - properties: - enabled: - description: Alert enabled or disabled - type: boolean -# threshold: -# description: Threshold for alert going on/off -# type: string - x-kubernetes-preserve-unknown-fields: true - bdb_high_throughput: - description: "Throughput is higher than specified threshold value [requests / sec.] - -Note threshold is commented (allow string/int/float and support backwards compatibility) but is required" - properties: - enabled: - description: Alert enabled or disabled - type: boolean -# threshold: -# description: Threshold for alert going on/off -# type: string - x-kubernetes-preserve-unknown-fields: true - bdb_long_running_action: - description: "An alert for state-machines that are running for too long - -Note threshold is commented (allow string/int/float and support backwards compatibility) but is required" - properties: - enabled: - description: Alert enabled or disabled - type: boolean -# threshold: -# description: Threshold for alert going on/off -# type: string - x-kubernetes-preserve-unknown-fields: true - bdb_low_throughput: - description: "Throughput is lower than specified threshold value [requests / sec.] - -Note threshold is commented (allow string/int/float and support backwards compatibility) but is required" - properties: - enabled: - description: Alert enabled or disabled - type: boolean -# threshold: -# description: Threshold for alert going on/off -# type: string - x-kubernetes-preserve-unknown-fields: true - bdb_ram_dataset_overhead: - description: "Dataset RAM overhead of a shard has reached the threshold value [% of its RAM limit] - -Note threshold is commented (allow string/int/float and support backwards compatibility) but is required" - properties: - enabled: - description: Alert enabled or disabled - type: boolean -# threshold: -# description: Threshold for alert going on/off -# type: string - x-kubernetes-preserve-unknown-fields: true - bdb_ram_values: - description: "Percent of values kept in a shard's RAM is lower than [% of its key count] - -Note threshold is commented (allow string/int/float and support backwards compatibility) but is required" - properties: - enabled: - description: Alert enabled or disabled - type: boolean -# threshold: -# description: Threshold for alert going on/off -# type: string - x-kubernetes-preserve-unknown-fields: true - bdb_replica_src_high_syncer_lag: - description: "Replica-of source - sync lag is higher than specified threshold value [seconds] - -Note threshold is commented (allow string/int/float and support backwards compatibility) but is required" - properties: - enabled: - description: Alert enabled or disabled - type: boolean -# threshold: -# description: Threshold for alert going on/off -# type: string - x-kubernetes-preserve-unknown-fields: true - bdb_replica_src_syncer_connection_error: - description: "Replica-of source - sync has connection error while trying to connect replica source - -Note threshold is commented (allow string/int/float and support backwards compatibility) but is required" - properties: - enabled: - description: Alert enabled or disabled - type: boolean -# threshold: -# description: Threshold for alert going on/off -# type: string - x-kubernetes-preserve-unknown-fields: true - bdb_shard_num_ram_values: - description: "Number of values kept in a shard's RAM is lower than [values] - -Note threshold is commented (allow string/int/float and support backwards compatibility) but is required" - properties: - enabled: - description: Alert enabled or disabled - type: boolean -# threshold: -# description: Threshold for alert going on/off -# type: string - x-kubernetes-preserve-unknown-fields: true - bdb_size: - description: "Dataset size has reached the threshold value [% of the memory limit] expected fields: - -Note threshold is commented (allow string/int/float and support backwards compatibility) but is required" - properties: - enabled: - description: Alert enabled or disabled - type: boolean -# threshold: -# description: Threshold for alert going on/off -# type: string - x-kubernetes-preserve-unknown-fields: true - type: object - backup: - description: Target for automatic database backups. - properties: - abs: + additionalPrinterColumns: + - jsonPath: .status.version + name: Version + type: string + - jsonPath: .status.internalEndpoints[*].port + name: Port + type: string + - jsonPath: .status.redisEnterpriseCluster + name: Cluster + type: string + - jsonPath: .status.shardStatuses.active + name: Shards + type: string + - jsonPath: .status.status + name: Status + type: string + - jsonPath: .status.specStatus + name: Spec Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + subresources: + status: {} + schema: + openAPIV3Schema: + description: RedisEnterpriseDatabase is the Schema for the redisenterprisedatabases + API + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + description: RedisEnterpriseDatabaseSpec defines the desired state of RedisEnterpriseDatabase + properties: + alertSettings: + description: Settings for database alerts + properties: + bdb_backup_delayed: + description: "Periodic backup has been delayed for longer than specified threshold value [minutes]. + -Note threshold is commented (allow string/int/float and support backwards compatibility) but is required" + properties: + enabled: + description: Alert enabled or disabled + type: boolean + # threshold: + # description: Threshold for alert going on/off + # type: string + x-kubernetes-preserve-unknown-fields: true + type: object + bdb_crdt_src_high_syncer_lag: + description: "Active-active source - sync lag is higher than specified threshold value [seconds] + -Note threshold is commented (allow string/int/float and support backwards compatibility) but is required" + properties: + enabled: + description: Alert enabled or disabled + type: boolean + # threshold: + # description: Threshold for alert going on/off + # type: string + x-kubernetes-preserve-unknown-fields: true + type: object + bdb_crdt_src_syncer_connection_error: + description: "Active-active source - sync has connection error while trying to connect replica source + -Note threshold is commented (allow string/int/float and support backwards compatibility) but is required" + properties: + enabled: + description: Alert enabled or disabled + type: boolean + # threshold: + # description: Threshold for alert going on/off + # type: string + x-kubernetes-preserve-unknown-fields: true + type: object + bdb_crdt_src_syncer_general_error: + description: "Active-active source - sync encountered in general error + -Note threshold is commented (allow string/int/float and support backwards compatibility) but is required" + properties: + enabled: + description: Alert enabled or disabled + type: boolean + # threshold: + # description: Threshold for alert going on/off + # type: string + x-kubernetes-preserve-unknown-fields: true + type: object + bdb_high_latency: + description: "Latency is higher than specified threshold value [micro-sec] + -Note threshold is commented (allow string/int/float and support backwards compatibility) but is required" + properties: + enabled: + description: Alert enabled or disabled + type: boolean + # threshold: + # description: Threshold for alert going on/off + # type: string + x-kubernetes-preserve-unknown-fields: true + type: object + bdb_high_throughput: + description: "Throughput is higher than specified threshold value [requests / sec.] + -Note threshold is commented (allow string/int/float and support backwards compatibility) but is required" + properties: + enabled: + description: Alert enabled or disabled + type: boolean + # threshold: + # description: Threshold for alert going on/off + # type: string + x-kubernetes-preserve-unknown-fields: true + type: object + bdb_long_running_action: + description: "An alert for state-machines that are running for too long + -Note threshold is commented (allow string/int/float and support backwards compatibility) but is required" + properties: + enabled: + description: Alert enabled or disabled + type: boolean + # threshold: + # description: Threshold for alert going on/off + # type: string + x-kubernetes-preserve-unknown-fields: true + type: object + bdb_low_throughput: + description: "Throughput is lower than specified threshold value [requests / sec.] + -Note threshold is commented (allow string/int/float and support backwards compatibility) but is required" + properties: + enabled: + description: Alert enabled or disabled + type: boolean + # threshold: + # description: Threshold for alert going on/off + # type: string + x-kubernetes-preserve-unknown-fields: true + type: object + bdb_ram_dataset_overhead: + description: "Dataset RAM overhead of a shard has reached the threshold value [% of its RAM limit] + -Note threshold is commented (allow string/int/float and support backwards compatibility) but is required" + properties: + enabled: + description: Alert enabled or disabled + type: boolean + # threshold: + # description: Threshold for alert going on/off + # type: string + x-kubernetes-preserve-unknown-fields: true + type: object + bdb_ram_values: + description: "Percent of values kept in a shard's RAM is lower than [% of its key count] + -Note threshold is commented (allow string/int/float and support backwards compatibility) but is required" + properties: + enabled: + description: Alert enabled or disabled + type: boolean + # threshold: + # description: Threshold for alert going on/off + # type: string + x-kubernetes-preserve-unknown-fields: true + type: object + bdb_replica_src_high_syncer_lag: + description: "Replica-of source - sync lag is higher than specified threshold value [seconds] + -Note threshold is commented (allow string/int/float and support backwards compatibility) but is required" + properties: + enabled: + description: Alert enabled or disabled + type: boolean + # threshold: + # description: Threshold for alert going on/off + # type: string + x-kubernetes-preserve-unknown-fields: true + type: object + bdb_replica_src_syncer_connection_error: + description: "Replica-of source - sync has connection error while trying to connect replica source + -Note threshold is commented (allow string/int/float and support backwards compatibility) but is required" + properties: + enabled: + description: Alert enabled or disabled + type: boolean + # threshold: + # description: Threshold for alert going on/off + # type: string + x-kubernetes-preserve-unknown-fields: true + type: object + bdb_shard_num_ram_values: + description: "Number of values kept in a shard's RAM is lower than [values] + -Note threshold is commented (allow string/int/float and support backwards compatibility) but is required" + properties: + enabled: + description: Alert enabled or disabled + type: boolean + # threshold: + # description: Threshold for alert going on/off + # type: string + x-kubernetes-preserve-unknown-fields: true + type: object + bdb_size: + description: "Dataset size has reached the threshold value [% of the memory limit] expected fields: + -Note threshold is commented (allow string/int/float and support backwards compatibility) but is required" + properties: + enabled: + description: Alert enabled or disabled + type: boolean + # threshold: + # description: Threshold for alert going on/off + # type: string + x-kubernetes-preserve-unknown-fields: true + type: object + type: object + backup: + description: Target for automatic database backups. + properties: + abs: + properties: + absSecretName: + description: The name of the K8s secret that holds ABS credentials. + The secret must contain the keys "AccountName" and "AccountKey", + and these must hold the corresponding credentials + type: string + container: + description: Azure Blob Storage container name. + type: string + subdir: + description: Optional. Azure Blob Storage subdir under container. + type: string + required: + - absSecretName + - container + type: object + ftp: + properties: + url: + description: "a URI of the ftps://[USER[:PASSWORD]@]HOST[:PORT]/PATH[/]" + type: string + pattern: ftps?://(([^@]+)@)?([^@/:]+)(:(\d+))?([/\.]/?[^@/\.]+)*?/?$ + required: + - url + type: object + gcs: + description: GoogleStorage + properties: + bucketName: + description: Google Storage bucket name. + type: string + gcsSecretName: + description: The name of the K8s secret that holds the Google + Cloud Storage credentials. The secret must contain the keys + "CLIENT_ID", "PRIVATE_KEY", "PRIVATE_KEY_ID", "CLIENT_EMAIL" + and these must hold the corresponding credentials. The keys + should correspond to the values in the key JSON. + type: string + subdir: + description: Optional. Google Storage subdir under bucket. + type: string + required: + - bucketName + - gcsSecretName + type: object + interval: + description: Backup Interval in seconds + type: integer + mount: + description: MountPointStorage + properties: + path: + description: Path to the local mount point. You must create + the mount point on all nodes, and the redislabs:redislabs + user must have read and write permissions on the local mount + point. + type: string + required: + - path + type: object + s3: + properties: + awsSecretName: + description: The name of the K8s secret that holds the AWS credentials. + The secret must contain the keys "AWS_ACCESS_KEY_ID" and "AWS_SECRET_ACCESS_KEY", + and these must hold the corresponding credentials. + type: string + bucketName: + description: Amazon S3 bucket name. + type: string + subdir: + description: Optional. Amazon S3 subdir under bucket. + type: string + required: + - awsSecretName + - bucketName + type: object + sftp: + properties: + sftp_url: + description: SFTP url + type: string + pattern: ^sftp://(([^@]+)@)?([^@/:]+)(:(\d+))?(/([^@/\.]+[/\.]?)*)?$ + sftpSecretName: + description: The name of the K8s secret that holds SFTP credentials. + The secret must contain the "Key" key, which is the SSH private + key for connecting to the sftp server. + type: string + required: + - sftpSecretName + - sftp_url + type: object + swift: + properties: + auth_url: + description: Swift service authentication URL. + type: string + pattern: ^https?://(([^@]+)@)?([^@/:]+)(:(\d+))?([/\.]([^@/\.]+))*?/?$ + container: + description: Swift object store container for storing the backup + files. + type: string + prefix: + description: Optional. Prefix (path) of backup files in the + swift container. + type: string + swiftSecretName: + description: 'The name of the K8s secret that holds Swift credentials. + The secret must contain the keys "Key" and "User", and these + must hold the corresponding credentials: service access key + and service user name (pattern for the latter does not allow + special characters &,<,>,")' + type: string + required: + - auth_url + - container + - swiftSecretName + type: object + type: object + clientAuthenticationCertificates: + description: The Secrets containing TLS Client Certificate to use for + Authentication + items: + type: string + type: array + dataInternodeEncryption: + description: Internode encryption (INE) setting. An optional boolean setting, overriding + a similar cluster-wide policy. + If set to False, INE is guaranteed to be turned off for this DB (regardless of cluster-wide policy). + If set to True, INE will be turned on, unless the capability is not supported by the DB ( + in such a case we will get an error and database creation will fail). + If left unspecified, will be disabled if internode encryption is not supported by the DB + (regardless of cluster default). + Deleting this property after explicitly setting its value shall have no effect. + type: boolean + databasePort: + description: Database port number. TCP port on which the database is + available. Will be generated automatically if omitted. can not be + changed after creation + type: integer + databaseSecretName: + description: The name of the K8s secret that holds the password to the + database. + type: string + defaultUser: + description: Is connecting with a default user allowed? + type: boolean + evictionPolicy: + description: Database eviction policy. see more https://docs.redislabs.com/latest/rs/administering/database-operations/eviction-policy/ + type: string + memorySize: + description: memory size of database. use formats like 100MB, 0.1GB. + minimum value in 100MB. + type: string + modulesList: + description: List of modules associated with database + items: + description: 'Redis Enterprise Module: https://redislabs.com/redis-enterprise/modules/' properties: - absSecretName: - description: The name of the K8s secret that holds ABS credentials. - The secret must contain the keys "AccountName" and "AccountKey", - and these must hold the corresponding credentials + config: + description: Module command line arguments e.g. VKEY_MAX_ENTITY_COUNT + 30 type: string - container: - description: Azure Blob Storage container name. + name: + description: The module's name e.g "ft" for redissearch type: string - subdir: - description: Optional. Azure Blob Storage subdir under container. + version: + description: Module's semantic version e.g "1.6.12" type: string required: - - absSecretName - - container + - name + - version type: object - ftp: + type: array + ossCluster: + description: OSS Cluster mode option. Note that not all client libraries + support OSS cluster mode. + type: boolean + persistence: + description: Database on-disk persistence policy + enum: + - disabled + - aofEverySecond + - aofAlways + - snapshotEvery1Hour + - snapshotEvery6Hour + - snapshotEvery12Hour + type: string + proxyPolicy: + description: 'The policy used for proxy binding to the endpoint. Supported + proxy policies are: single/all-master-shards/all-nodes When left blank, + the default value will be chosen according to the value of ossCluster + - single if disabled, all-master-shards when enabled' + type: string + rackAware: + description: 'Whether database should be rack aware. This improves availability + - more information: https://docs.redislabs.com/latest/rs/concepts/high-availability/rack-zone-awareness/' + type: boolean + redisEnterpriseCluster: + description: Connection to Redis Enterprise Cluster + properties: + name: + description: The name of the Redis Enterprise Cluster where the + database should be stored. + type: string + required: + - name + type: object + replicaSources: + description: What databases to replicate from + items: properties: - url: - description: "a URI of the ftps://[USER[:PASSWORD]@]HOST[:PORT]/PATH[/]" + clientKeySecret: + description: Secret that defines what client key to use. The + secret needs 2 keys in it's map, "cert" that is the PEM encoded + certificate and "key" that is the PEM encoded private key type: string - pattern: ftps?://(([^@]+)@)?([^@/:]+)(:(\d+))?([/\.]/?[^@/\.]+)*?/?$ - required: - - url - type: object - gcs: - description: GoogleStorage - properties: - bucketName: - description: Google Storage bucket name. + compression: + description: GZIP Compression level (0-9) to use for replication + type: integer + replicaSourceName: + description: Kubernetes resource name of type ReplicaSourceType type: string - gcsSecretName: - description: The name of the K8s secret that holds the Google - Cloud Storage credentials. The secret must contain the keys - "CLIENT_ID", "PRIVATE_KEY", "PRIVATE_KEY_ID", "CLIENT_EMAIL" - and these must hold the corresponding credentials. The keys - should correspond to the values in the key JSON. + replicaSourceType: + description: Determines what Kuberetes resource ReplicaSourceName + refers to SECRET - Get URI from secret named in ReplicaSourceName. The + secret will have a uri key that defines the complete, redis:// + URI REDB - Determine URI from Kubernetes REDB resource named + in ReplicaSourceName type: string - subdir: - description: Optional. Google Storage subdir under bucket. + serverCertSecret: + description: Secret that defines the Server's certificate. The + secret needs 1 key in it's map, "cert" that is the PEM encoded + certificate type: string - required: - - bucketName - - gcsSecretName - type: object - interval: - description: Backup Interval in seconds - type: integer - mount: - description: MountPointStorage - properties: - path: - description: Path to the local mount point. You must create - the mount point on all nodes, and the redislabs:redislabs - user must have read and write permissions on the local mount - point. + tlsSniName: + description: TLS SNI Name to use type: string required: - - path + - replicaSourceName + - replicaSourceType type: object - s3: + type: array + replication: + description: In-memory database replication. When enabled, database + will have replica shard for every master - leading to higher availability. + type: boolean + rolesPermissions: + description: List of Redis Enteprise ACL and Role bindings to apply + items: + description: Redis Enterprise Role and ACL Binding properties: - awsSecretName: - description: The name of the K8s secret that holds the AWS credentials. - The secret must contain the keys "AWS_ACCESS_KEY_ID" and "AWS_SECRET_ACCESS_KEY", - and these must hold the corresponding credentials. + acl: + description: Acl Name of RolePermissionType type: string - bucketName: - description: Amazon S3 bucket name. + role: + description: Role Name of RolePermissionType type: string - subdir: - description: Optional. Amazon S3 subdir under bucket. + type: + description: Type of Redis Enterprise Database Role Permission type: string required: - - awsSecretName - - bucketName + - acl + - role + - type type: object - sftp: + type: array + shardCount: + description: Number of database server-side shards + type: integer + shardsPlacement: + description: Control the density of shards - should they reside on as few or as many nodes as possible. + Available options are "dense" or "sparse". If left unset, defaults to "dense". + enum: + - dense + - sparse + type: string + tlsMode: + description: Require SSL authenticated and encrypted connections to + the database. enabled - all incoming connections to the Database must + use SSL. disabled - no incoming connection to the Database should + use SSL. replica_ssl - databases that replicate from this one need + to use SSL. + enum: + - disabled + - enabled + - replica_ssl + type: string + type: object + status: + description: RedisEnterpriseDatabaseStatus defines the observed state of + RedisEnterpriseDatabase + properties: + createdTime: + description: Time when the database was created + type: string + databaseUID: + description: Database UID provided by redis enterprise + type: string + internalEndpoints: + description: Endpoints listed internally by the Redis Enterprise Cluster. + Can be used to correlate a ReplicaSourceStatus entry. + items: properties: - sftp_url: - description: SFTP url - type: string - pattern: ^sftp://(([^@]+)@)?([^@/:]+)(:(\d+))?(/([^@/\.]+[/\.]?)*)?$ - sftpSecretName: - description: The name of the K8s secret that holds SFTP credentials. - The secret must contain the "Key" key, which is the SSH private - key for connecting to the sftp server. + host: + description: Hostname assigned to the database type: string - required: - - sftpSecretName - - sftp_url + port: + description: Database port name + type: integer type: object - swift: + type: array + lastActionStatus: + description: Status of the last action done by operator on this database + type: string + lastActionUid: + description: UID of the last action done by operator on this database + type: string + lastUpdated: + description: Time when the database was last updated + type: string + observedGeneration: + description: 'The generation (built in update counter of K8s) of the + REDB resource that was fully acted upon, meaning that all changes + were handled and sent as an API call to the Redis Enterprise Cluster + (REC). This field value should equal the current generation when the + resource changes were handled. Note: the lastActionStatus field tracks + actions handled asynchronously by the Redis Enterprise Cluster.' + format: int64 + type: integer + redisEnterpriseCluster: + description: The Redis Enterprise Cluster Object this Resource is associated + with + type: string + replicaSourceStatuses: + description: ReplicaSource statuses + items: properties: - auth_url: - description: Swift service authentication URL. + endpointHost: + description: The internal host name of the replica source database. + Can be used as an identifier. See the internalEndpoints list + on the REDB status. type: string - pattern: ^https?://(([^@]+)@)?([^@/:]+)(:(\d+))?([/\.]([^@/\.]+))*?/?$ - container: - description: Swift object store container for storing the backup - files. + lag: + description: Lag in millisec between source and destination (while + synced). + type: integer + lastError: + description: Last error encountered when syncing from the source. type: string - prefix: - description: Optional. Prefix (path) of backup files in the - swift container. + lastUpdate: + description: Time when we last receive an update from the source. type: string - swiftSecretName: - description: 'The name of the K8s secret that holds Swift credentials. - The secret must contain the keys "Key" and "User", and these - must hold the corresponding credentials: service access key - and service user name (pattern for the latter does not allow - special characters &,<,>,")' + rdbSize: + description: The source’s RDB size to be transferred during the + syncing phase. + type: integer + rdbTransferred: + description: Number of bytes transferred from the source’s RDB + during the syncing phase. + type: integer + status: + description: Sync status of this source type: string required: - - auth_url - - container - - swiftSecretName + - endpointHost type: object - type: object - clientAuthenticationCertificates: - description: The Secrets containing TLS Client Certificate to use for - Authentication - items: - type: string - type: array - databaseSecretName: - description: The name of the K8s secret that holds the password to the - database. - type: string - defaultUser: - description: Is connecting with a default user allowed? - type: boolean - ossCluster: - description: OSS Cluster mode option - type: boolean - proxyPolicy: - description: The policy used for proxy binding to the endpoint - type: string - evictionPolicy: - description: Database eviction policy. see more https://docs.redislabs.com/latest/rs/administering/database-operations/eviction-policy/ - type: string - memorySize: - description: memory size of database. use formats like 100MB, 0.1GB. - minimum value in 100MB. - type: string - modulesList: - description: List of modules associated with database - items: - description: 'Redis Enterprise Module: https://redislabs.com/redis-enterprise/modules/' - properties: - config: - description: Module command line arguments e.g. VKEY_MAX_ENTITY_COUNT - 30 - type: string - name: - description: The module's name e.g "ft" for redissearch - type: string - version: - description: Module's semantic version e.g "1.6.12" - type: string - required: - - name - - version - type: object - type: array - persistence: - description: Database on-disk persistence policy - enum: - - disabled - - aofEverySecond - - aofAlways - - snapshotEvery1Hour - - snapshotEvery6Hour - - snapshotEvery12Hour - type: string - rackAware: - description: 'Whether database should be rack aware. This improves availability - - more information: https://docs.redislabs.com/latest/rs/concepts/high-availability/rack-zone-awareness/' - type: boolean - redisEnterpriseCluster: - description: Connection to Redis Enterprise Cluster - properties: - name: - description: The name of the Redis Enterprise Cluster where the - database should be stored. - type: string - required: - - name - type: object - replicaSources: - description: What databases to replicate from - items: - properties: - clientKeySecret: - description: Secret that defines what client key to use. The - secret needs 2 keys in it's map, "cert" that is the PEM encoded - certificate and "key" that is the PEM encoded private key - type: string - compression: - description: GZIP Compression level (0-9) to use for replication - type: integer - replicaSourceName: - description: Kubernetes resource name of type ReplicaSourceType - type: string - replicaSourceType: - description: Determines what Kuberetes resource ReplicaSourceName - refers to SECRET - Get URI from secret named in ReplicaSourceName. The - secret will have a uri key that defines the complete, redis:// - URI REDB - Determine URI from Kubernetes REDB resource named - in ReplicaSourceName - type: string - serverCertSecret: - description: Secret that defines the Server's certificate. The - secret needs 1 key in it's map, "cert" that is the PEM encoded - certificate - type: string - tlsSniName: - description: TLS SNI Name to use - type: string - required: - - replicaSourceName - - replicaSourceType - type: object - type: array - replication: - description: In-memory database replication. When enabled, database - will have replica shard for every master - leading to higher availability. - type: boolean - rolesPermissions: - description: List of Redis Enteprise ACL and Role bindings to apply - items: - description: Redis Enterprise Role and ACL Binding - properties: - acl: - description: Acl Name of RolePermissionType - type: string - role: - description: Role Name of RolePermissionType - type: string - type: - description: Type of Redis Enterprise Database Role Permission - type: string - required: - - acl - - role - - type + type: array + shardStatuses: + additionalProperties: + type: integer + description: Aggregated statuses of shards type: object - type: array - shardCount: - description: Number of database server-side shards - type: integer - tlsMode: - description: Require SSL authenticated and encrypted connections to - the database. enabled - all incoming connections to the Database must - use SSL. disabled - no incoming connection to the Database should - use SSL. replica_ssl - databases that replicate from this one need - to use SSL. - enum: - - disabled - - enabled - - replica_ssl - type: string - type: object - status: - description: RedisEnterpriseDatabaseStatus defines the observed state of - RedisEnterpriseDatabase - properties: - createdTime: - description: Time when the database was created - type: string - databaseUID: - description: Database UID provided by redis enterprise - type: string - internalEndpoints: - description: Endpoints listed internally by the Redis Enterprise Cluster. - Can be used to correlate a ReplicaSourceStatus entry. - items: + backupInfo: + description: Information on the database's periodic backup properties: - host: - description: Hostname assigned to the database + backupFailureReason: + description: Reason of last failed backup process type: string - port: - description: Database port name + backupHistory: + description: Backup history retention policy (number of days, + 0 is forever) type: integer - type: object - type: array - lastActionStatus: - description: Status of the last action done by operator on this database - type: string - lastActionUid: - description: UID of the last action done by operator on this database - type: string - lastUpdated: - description: Time when the database was last updated - type: string - observedGeneration: - description: 'The generation (built in update counter of K8s) of the - REDB resource that was fully acted upon, meaning that all changes - were handled and sent as an API call to the Redis Enterprise Cluster - (REC). This field value should equal the current generation when the - resource changes were handled. Note: the lastActionStatus field tracks - actions handled asynchronously by the Redis Enterprise Cluster.' - format: int64 - type: integer - redisEnterpriseCluster: - description: The Redis Enterprise Cluster Object this Resource is associated - with - type: string - replicaSourceStatuses: - description: ReplicaSource statuses - items: - properties: - endpointHost: - description: The internal host name of the replica source database. - Can be used as an identifier. See the internalEndpoints list - on the REDB status. - type: string - lag: - description: Lag in millisec between source and destination (while - synced). + backupInterval: + description: Interval in seconds in which automatic backup will + be initiated type: integer - lastError: - description: Last error encountered when syncing from the source. - type: string - lastUpdate: - description: Time when we last receive an update from the source. - type: string - rdbSize: - description: The source’s RDB size to be transferred during the - syncing phase. + backupIntervalOffset: + description: Offset (in seconds) from round backup interval when + automatic backup will be initiated (should be less than backup_interval) type: integer - rdbTransferred: - description: Number of bytes transferred from the source’s RDB - during the syncing phase. + backupProgressPercentage: + description: Database scheduled periodic backup progress (percentage) type: integer - status: - description: Sync status of this source + backupStatus: + description: Status of scheduled periodic backup process + type: string + lastBackupTime: + description: Time of last successful backup type: string required: - - endpointHost + - backupHistory type: object - type: array - shardStatuses: - additionalProperties: - type: integer - description: Aggregated statuses of shards - type: object - specStatus: - description: Whether the desired specification is valid - type: string - status: - description: The status of the database - type: string - version: - description: Database compatibility version - type: string - type: object - type: object + specStatus: + description: Whether the desired specification is valid + type: string + status: + description: The status of the database + type: string + version: + description: Database compatibility version + type: string + type: object + type: object diff --git a/chart/redis-operator/templates/_helpers.tpl b/chart/redis-operator/templates/_helpers.tpl index 16d0c95..24b10bb 100644 --- a/chart/redis-operator/templates/_helpers.tpl +++ b/chart/redis-operator/templates/_helpers.tpl @@ -53,6 +53,7 @@ do echo "Waiting for redis enterprise operator to be active"; STATE=$(kubectl get --namespace="{{ .Release.Namespace }}" rec/redis-enterprise -o jsonpath='{.status.state}') ; sleep 5; done + echo "Redia Enterprise Cluster resource is Active" ' name: wait-for-cr-patch-created diff --git a/chart/redis-operator/templates/application.yaml b/chart/redis-operator/templates/application.yaml index 63c5af1..2f994ee 100644 --- a/chart/redis-operator/templates/application.yaml +++ b/chart/redis-operator/templates/application.yaml @@ -26,7 +26,8 @@ spec: #### Before deleting the Application, make sure to delete any Custom Resources from the K8s cluster: ##### 1. first any REDB resources ##### 2. followed by deleting the `rec` (instance of REC) - ##### 3. then the Application can be safely Deleted + ##### 3. then delete the ValidatingWebhookConfiguration named `redb.admission.redislabs` + ##### 4. only then the Application can be safely Deleted selector: matchLabels: app.kubernetes.io/name: "{{ .Release.Name }}" diff --git a/chart/redis-operator/templates/configmap/cr.yaml b/chart/redis-operator/templates/configmap/cr.yaml index 3da706d..50e7b87 100644 --- a/chart/redis-operator/templates/configmap/cr.yaml +++ b/chart/redis-operator/templates/configmap/cr.yaml @@ -7,7 +7,7 @@ metadata: app.kubernetes.io/component: cr-configmap data: redis-enterprise-cluster.yaml: |- - apiVersion: "app.redislabs.com/v1alpha1" + apiVersion: "app.redislabs.com/v1" kind: "RedisEnterpriseCluster" metadata: name: "redis-enterprise" diff --git a/chart/redis-operator/templates/deployment/operator.yaml b/chart/redis-operator/templates/deployment/operator.yaml index 7c00146..8a74cc5 100644 --- a/chart/redis-operator/templates/deployment/operator.yaml +++ b/chart/redis-operator/templates/deployment/operator.yaml @@ -22,7 +22,17 @@ spec: command: - redis-enterprise-operator imagePullPolicy: Always + envFrom: + - configMapRef: + name: operator-environment-config + optional: true + ports: + - containerPort: 8080 env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace - name: WATCH_NAMESPACE valueFrom: fieldRef: @@ -33,8 +43,6 @@ spec: fieldPath: metadata.name - name: OPERATOR_NAME value: redis-enterprise-operator - - name: DATABASE_CONTROLLER_ENABLED - value: "true" resources: limits: cpu: 4000m @@ -42,6 +50,56 @@ spec: requests: cpu: 500m memory: 256Mi + livenessProbe: + failureThreshold: 3 + successThreshold: 1 + periodSeconds: 5 + timeoutSeconds: 10 + httpGet: + path: /healthz + port: 8080 + scheme: HTTP + - name: admission + image: {{ .Values.operator.image.repository }}:{{ .Values.operator.image.tag }} + command: + - /usr/local/bin/admission + imagePullPolicy: Always + envFrom: + - configMapRef: + name: operator-environment-config + optional: true + ports: + - containerPort: 443 + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + resources: + limits: + cpu: 1000m + memory: 512Mi + requests: + cpu: 250m + memory: 256Mi + readinessProbe: + failureThreshold: 3 + successThreshold: 1 + periodSeconds: 5 + timeoutSeconds: 10 + httpGet: + path: /healthz + port: 8443 + scheme: HTTPS + livenessProbe: + failureThreshold: 3 + successThreshold: 1 + periodSeconds: 5 + timeoutSeconds: 10 + httpGet: + path: /liveness + port: 8443 + scheme: HTTPS - name: usage-meter image: {{ .Values.usagemeter.image }}:{{ .Values.usagemeter.tag }} imagePullPolicy: Always diff --git a/chart/redis-operator/templates/job/cr-create.yaml b/chart/redis-operator/templates/job/cr-create.yaml index 393a60c..c24a6b3 100644 --- a/chart/redis-operator/templates/job/cr-create.yaml +++ b/chart/redis-operator/templates/job/cr-create.yaml @@ -24,6 +24,35 @@ spec: then kubectl apply -f /cr_to_create/redis-enterprise-cluster.yaml fi + until kubectl get secret admission-tls; + do echo "Waiting for admission-tls secret"; sleep 5; + done + echo "found admission-tls secret" + CERT=$(kubectl get secret admission-tls -o jsonpath='{.data.cert}') + kubectl apply -f - << EOF + apiVersion: admissionregistration.k8s.io/v1 + kind: ValidatingWebhookConfiguration + metadata: + name: redb-admission + webhooks: + - name: redb.admission.redislabs + failurePolicy: Fail + matchPolicy: Exact + sideEffects: None + timeoutSeconds: 30 + rules: + - apiGroups: ["app.redislabs.com"] + apiVersions: ["v1alpha1"] + operations: ["*"] + resources: ["redisenterprisedatabases"] + clientConfig: + service: + namespace: {{ .Release.Namespace }} + name: admission + path: /admission + caBundle: $CERT + admissionReviewVersions: ["v1", "v1beta1"] + EOF image: {{ .Values.deployerHelm.image }} imagePullPolicy: Always name: cr-create diff --git a/schema.yaml b/schema.yaml index e9ea76a..402c556 100644 --- a/schema.yaml +++ b/schema.yaml @@ -65,46 +65,52 @@ properties: rules: - apiGroups: ["rbac.authorization.k8s.io", ""] resources: ["roles", "serviceaccounts", "rolebindings"] - verbs: ["*"] + verbs: ["bind", "escalate", "impersonate", "userextras", + "create", "get", "list", "watch", "update", "patch", + "delete", "deletecollection"] - apiGroups: - app.redislabs.com resources: - - "*" - verbs: - - "*" + - "redisenterpriseclusters" + - "redisenterpriseclusters/status" + - "redisenterpriseclusters/finalizers" + - "redisenterprisedatabases" + - "redisenterprisedatabases/status" + - "redisenterprisedatabases/finalizers" + verbs: ["delete", "deletecollection", "get", "list", "patch", + "create", "update", "watch"] - apiGroups: [""] resources: ["secrets"] - verbs: ["*"] + verbs: ["update", "get", "read", "list", "listallnamespaces", + "watch", "watchlist", "watchlistallnamespaces", "create", + "patch", "replace", "delete", "deletecollection"] - apiGroups: [""] resources: ["endpoints"] verbs: ["get", "list", "watch"] - apiGroups: [""] resources: ["events"] - verbs: ["create"] + verbs: ["create", "patch"] - apiGroups: ["apps"] resources: ["deployments", "statefulsets", "replicasets"] - verbs: ["*"] + verbs: ["create", "delete", "deletecollection", "get", "list", + "patch", "update", "watch"] - apiGroups: ["policy"] resources: ["poddisruptionbudgets"] - verbs: ["create", "delete", "get"] + verbs: ["create", "delete", "get", "list", "watch"] - apiGroups: [""] resources: ["configmaps"] verbs: ["create", "delete", "get" , "update", "list", "watch"] - apiGroups: [""] resources: ["persistentvolumeclaims"] - verbs: ["create", "delete", "get" , "update", "list"] + verbs: ["create", "delete", "get" , "update", "list", "watch"] # needed rbac rules for services controller - apiGroups: [""] resources: ["pods"] - verbs: ["get", "watch", "list", "update", "patch"] + verbs: ["get", "watch", "list", "update", "patch", "delete"] - apiGroups: [""] resources: ["services"] verbs: ["get", "watch", "list", "update", "patch", "create", "delete"] - - apiGroups: - - route.openshift.io - resources: ["routes", "routes/custom-host"] - verbs: ["*"] - - apiGroups: ["extensions"] + - apiGroups: ["policy"] resources: ["podsecuritypolicies"] resourceNames: - redis-enterprise-psp @@ -112,7 +118,16 @@ properties: - use - apiGroups: ["extensions"] resources: ["ingresses"] - verbs: ["*"] + verbs: ["create", "patch", "replace", "delete", + "deletecollection", "read", "list", "listallnamespaces", + "watch", "watchlist", "watchlistallnamespaces", + "patchstatus", "readstatus", "replacestatus", "update"] + - apiGroups: ["networking.k8s.io"] + resources: ["ingresses"] + verbs: ["create", "patch", "replace", "delete", + "deletecollection", "read", "list", "listallnamespaces", + "watch", "watchlist", "watchlistallnamespaces", + "patchstatus", "readstatus", "replacestatus", "update"] DeployJobServiceAccount: type: string title: Service account for custom resources From d959749f7bbbd2e4656556e5be3e3c6df9f0a461 Mon Sep 17 00:00:00 2001 From: Nick Terner Date: Mon, 21 Mar 2022 20:00:03 +0200 Subject: [PATCH 090/120] update latest operator build --- Makefile | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index b7a3708..56c45cb 100644 --- a/Makefile +++ b/Makefile @@ -24,7 +24,7 @@ $(info ---- CHART_NAME = $(CHART_NAME)) REDIS_TAG ?= 6.2.10-83 $(info ---- REDIS_TAG = $(REDIS_TAG)) -OPERATOR_TAG ?= 6.2.10-3 +OPERATOR_TAG ?= 6.2.10-4 $(info ---- OPERATOR_TAG = $(OPERATOR_TAG)) # The repo to pull the operator image from Docker Hub registry. diff --git a/README.md b/README.md index c490ee7..fdba58c 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ Redis version tags are in the format Major.Minor.Patch-Sub but GKE Marketplace r ```shell export APP_INSTANCE_NAME=redis-enterprise-operator export NAMESPACE=redis -export TAG=6.2.10-3 +export TAG=6.2.10-4 export DEPLOYER_TAG=6.021001 export REPO=gcr.io/cloud-marketplace/redislabs-public/redis-enterprise ``` From 86c96c6f8a8101174dc6622183ad794ec8d067bd Mon Sep 17 00:00:00 2001 From: Nick Terner Date: Tue, 22 Mar 2022 00:49:49 +0200 Subject: [PATCH 091/120] move crds from configmap to container helm charts apparently have a 1M limit and the rec_crd would take most of it. moreover, configmaps can't be over ~200K --- chart/redis-operator/templates/_helpers.tpl | 6 +----- chart/redis-operator/templates/configmap/crd.yaml | 13 ------------- chart/redis-operator/templates/job/crd-create.yaml | 7 ------- deployer/Dockerfile | 4 ++++ .../files/crd => deployer}/rec_crd.yaml | 0 .../files/crd => deployer}/redb_crd.yaml | 0 6 files changed, 5 insertions(+), 25 deletions(-) delete mode 100644 chart/redis-operator/templates/configmap/crd.yaml rename {chart/redis-operator/files/crd => deployer}/rec_crd.yaml (100%) rename {chart/redis-operator/files/crd => deployer}/redb_crd.yaml (100%) diff --git a/chart/redis-operator/templates/_helpers.tpl b/chart/redis-operator/templates/_helpers.tpl index 24b10bb..3a5bc56 100644 --- a/chart/redis-operator/templates/_helpers.tpl +++ b/chart/redis-operator/templates/_helpers.tpl @@ -1,7 +1,3 @@ -{{- define "redis_operator.CRDsConfigMap" -}} -{{- printf "%s-crd-config-map" .Release.Name | trunc 63 -}} -{{- end -}} - {{- define "redis_operator.CRDsJob" -}} {{- printf "%s-crd-job" .Release.Name | trunc 63 -}} {{- end -}} @@ -53,7 +49,7 @@ do echo "Waiting for redis enterprise operator to be active"; STATE=$(kubectl get --namespace="{{ .Release.Namespace }}" rec/redis-enterprise -o jsonpath='{.status.state}') ; sleep 5; done - echo "Redia Enterprise Cluster resource is Active" + echo "Redis Enterprise Cluster resource is Active" ' name: wait-for-cr-patch-created diff --git a/chart/redis-operator/templates/configmap/crd.yaml b/chart/redis-operator/templates/configmap/crd.yaml deleted file mode 100644 index f96d9ed..0000000 --- a/chart/redis-operator/templates/configmap/crd.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ template "redis_operator.CRDsConfigMap" . }} - labels: - app.kubernetes.io/name: {{ .Release.Name }} - app.kubernetes.io/component: crd-configmap -data: - {{- $root := . -}} - {{- range $path, $bytes := .Files.Glob "files/crd/*.yaml" }} - {{ base $path }}: |- - {{- $root.Files.Get $path | nindent 4 }} - {{- end -}} diff --git a/chart/redis-operator/templates/job/crd-create.yaml b/chart/redis-operator/templates/job/crd-create.yaml index 1c373fa..ebac07d 100644 --- a/chart/redis-operator/templates/job/crd-create.yaml +++ b/chart/redis-operator/templates/job/crd-create.yaml @@ -24,13 +24,6 @@ spec: image: {{ .Values.deployerHelm.image }} imagePullPolicy: Always name: crd-create - volumeMounts: - - name: crd-configmap - mountPath: /crd_to_create/ dnsPolicy: ClusterFirst restartPolicy: Never serviceAccountName: {{ .Values.CRDJobServiceAccount }} - volumes: - - name: crd-configmap - configMap: - name: {{ template "redis_operator.CRDsConfigMap" . }} diff --git a/deployer/Dockerfile b/deployer/Dockerfile index cd39359..a581f80 100644 --- a/deployer/Dockerfile +++ b/deployer/Dockerfile @@ -42,3 +42,7 @@ COPY --from=build /tmp/test/$CHART_NAME.tar.gz /data-test/chart/ COPY --from=build /tmp/apptest/schema.yaml /data-test/ COPY --from=build /tmp/schema.yaml /data/ +RUN mkdir -p /crd_to_create +ADD deployer/rec_crd.yaml /crd_to_create/rec_crd.yaml +ADD deployer/redb_crd.yaml /crd_to_create/redb_crd.yaml + diff --git a/chart/redis-operator/files/crd/rec_crd.yaml b/deployer/rec_crd.yaml similarity index 100% rename from chart/redis-operator/files/crd/rec_crd.yaml rename to deployer/rec_crd.yaml diff --git a/chart/redis-operator/files/crd/redb_crd.yaml b/deployer/redb_crd.yaml similarity index 100% rename from chart/redis-operator/files/crd/redb_crd.yaml rename to deployer/redb_crd.yaml From 5650d9a9aa7693f3232fed3ad3ddeffe00d10e2f Mon Sep 17 00:00:00 2001 From: Nick Terner Date: Tue, 22 Mar 2022 02:04:01 +0200 Subject: [PATCH 092/120] fix syntax/indent and operation order even though it's REDB (and not the REC) admission, prefer to set it up correctly first --- chart/redis-operator/templates/job/cr-create.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/chart/redis-operator/templates/job/cr-create.yaml b/chart/redis-operator/templates/job/cr-create.yaml index c24a6b3..9ccd378 100644 --- a/chart/redis-operator/templates/job/cr-create.yaml +++ b/chart/redis-operator/templates/job/cr-create.yaml @@ -20,10 +20,6 @@ spec: - "/bin/bash" - "-ec" - | - if ! kubectl get rec/redis-enterprise 2>/dev/null - then - kubectl apply -f /cr_to_create/redis-enterprise-cluster.yaml - fi until kubectl get secret admission-tls; do echo "Waiting for admission-tls secret"; sleep 5; done @@ -52,7 +48,11 @@ spec: path: /admission caBundle: $CERT admissionReviewVersions: ["v1", "v1beta1"] - EOF + EOF + if ! kubectl get rec/redis-enterprise 2>/dev/null + then + kubectl apply -f /cr_to_create/redis-enterprise-cluster.yaml + fi image: {{ .Values.deployerHelm.image }} imagePullPolicy: Always name: cr-create From be9517e9abf46eb1286776a58d8dea4f875aca28 Mon Sep 17 00:00:00 2001 From: Nick Terner Date: Tue, 22 Mar 2022 02:01:15 +0200 Subject: [PATCH 093/120] RBAC for job --- README.md | 10 ++++++++++ chart/redis-operator/templates/job/cr-create.yaml | 2 +- schema.yaml | 7 +++++-- testapp-role.yaml | 2 +- 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index fdba58c..7989ad7 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,13 @@ kubectl apply -f https://raw.githubusercontent.com/RedisLabs/redis-enterprise-k8 kubectl apply -f https://raw.githubusercontent.com/RedisLabs/redis-enterprise-k8s-docs/master/service_account.yaml ``` +Since `schema.yaml` configures RBAC for the jobs, but is not used even for this +kind of deployment, an alternative is needed. `testapp-*.yaml` files are the +namespaced Role portion, and this is the ClusterRole portion. Together these +cover the RBAC used by the service accounts of the various deployer jobs. +(In the bundle - generated via helm below - you may notice that the +`serviceAccountName` fields for the deployer accounts are blank.) + Create a cluster role for creating cluster scoped custom resources and checking their status. Use the spec below and save it in ```cluster-role.yaml``` ```shell @@ -129,6 +136,9 @@ kind: ClusterRole metadata: name: redis-operator-cluster-role rules: +- apiGroups: ["admissionregistration.k8s.io"] + resources: ["validatingwebhookconfigurations"] + verbs: ["*"] - apiGroups: ["apiextensions.k8s.io"] resources: ["customresourcedefinitions"] verbs: ["get", "create", "list", "patch"] diff --git a/chart/redis-operator/templates/job/cr-create.yaml b/chart/redis-operator/templates/job/cr-create.yaml index 9ccd378..4fe94c8 100644 --- a/chart/redis-operator/templates/job/cr-create.yaml +++ b/chart/redis-operator/templates/job/cr-create.yaml @@ -61,7 +61,7 @@ spec: mountPath: /cr_to_create/ dnsPolicy: ClusterFirst restartPolicy: OnFailure - serviceAccountName: {{ .Values.CRJobServiceAccount }} + serviceAccountName: {{ .Values.DeployJobServiceAccount }} volumes: - name: cr-configmap configMap: diff --git a/schema.yaml b/schema.yaml index 402c556..323fa78 100644 --- a/schema.yaml +++ b/schema.yaml @@ -134,7 +134,7 @@ properties: x-google-marketplace: type: SERVICE_ACCOUNT serviceAccount: - description: Service account for ownerReference chain + description: Service account for ownerReference chain and CR and admission creation roles: - type: ClusterRole rulesType: CUSTOM @@ -142,6 +142,9 @@ properties: - apiGroups: ["app.k8s.io", "apiextensions.k8s.io"] resources: ["*"] verbs: ["*"] + - apiGroups: ["admissionregistration.k8s.io"] + resources: ["validatingwebhookconfigurations"] + verbs: ["*"] - type: Role rulesType: CUSTOM rules: @@ -149,7 +152,7 @@ properties: resources: ["deployments"] verbs: ["*"] - apiGroups: [""] - resources: ["services"] + resources: ["services", "secrets"] verbs: ["*"] operator.replicas: type: integer diff --git a/testapp-role.yaml b/testapp-role.yaml index df11f5f..f258e08 100644 --- a/testapp-role.yaml +++ b/testapp-role.yaml @@ -10,5 +10,5 @@ rules: resources: ["applications"] verbs: ["*"] - apiGroups: [""] - resources: ["services"] + resources: ["services", "secrets"] verbs: ["*"] From f04ef7c98bb5814dbdad64b720b9ed41a994878e Mon Sep 17 00:00:00 2001 From: Nick Terner Date: Tue, 22 Mar 2022 02:54:47 +0200 Subject: [PATCH 094/120] update webhook name --- chart/redis-operator/templates/application.yaml | 2 +- chart/redis-operator/templates/job/cr-create.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/chart/redis-operator/templates/application.yaml b/chart/redis-operator/templates/application.yaml index 2f994ee..5b60aad 100644 --- a/chart/redis-operator/templates/application.yaml +++ b/chart/redis-operator/templates/application.yaml @@ -26,7 +26,7 @@ spec: #### Before deleting the Application, make sure to delete any Custom Resources from the K8s cluster: ##### 1. first any REDB resources ##### 2. followed by deleting the `rec` (instance of REC) - ##### 3. then delete the ValidatingWebhookConfiguration named `redb.admission.redislabs` + ##### 3. then delete the ValidatingWebhookConfiguration named `redb.admission.redis.com` ##### 4. only then the Application can be safely Deleted selector: matchLabels: diff --git a/chart/redis-operator/templates/job/cr-create.yaml b/chart/redis-operator/templates/job/cr-create.yaml index 4fe94c8..b10390a 100644 --- a/chart/redis-operator/templates/job/cr-create.yaml +++ b/chart/redis-operator/templates/job/cr-create.yaml @@ -29,9 +29,9 @@ spec: apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration metadata: - name: redb-admission + name: redb.admission.redis.com webhooks: - - name: redb.admission.redislabs + - name: redb.admission.redis.com failurePolicy: Fail matchPolicy: Exact sideEffects: None From e772f11c312c6400fa4a08b956fffa87de9398de Mon Sep 17 00:00:00 2001 From: Nick Terner Date: Tue, 22 Mar 2022 03:03:42 +0200 Subject: [PATCH 095/120] fix admission guessing our admission controller doesn't support v1 --- chart/redis-operator/templates/job/cr-create.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chart/redis-operator/templates/job/cr-create.yaml b/chart/redis-operator/templates/job/cr-create.yaml index b10390a..984bed4 100644 --- a/chart/redis-operator/templates/job/cr-create.yaml +++ b/chart/redis-operator/templates/job/cr-create.yaml @@ -47,7 +47,7 @@ spec: name: admission path: /admission caBundle: $CERT - admissionReviewVersions: ["v1", "v1beta1"] + admissionReviewVersions: ["v1beta1"] EOF if ! kubectl get rec/redis-enterprise 2>/dev/null then From 97e094636531941b89ba064f9d42d2ca0270fbf2 Mon Sep 17 00:00:00 2001 From: randv1r Date: Wed, 30 Mar 2022 13:21:03 +0300 Subject: [PATCH 096/120] adding wait for REDB crd to be created in init container --- chart/redis-operator/templates/_helpers.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chart/redis-operator/templates/_helpers.tpl b/chart/redis-operator/templates/_helpers.tpl index 3a5bc56..9f50385 100644 --- a/chart/redis-operator/templates/_helpers.tpl +++ b/chart/redis-operator/templates/_helpers.tpl @@ -29,7 +29,7 @@ - | timeout 120 bash -c ' COUNTER=0 - until kubectl get crd redisenterpriseclusters.app.redislabs.com; + until kubectl get crd redisenterpriseclusters.app.redislabs.com redisenterprisedatabases.app.redislabs.com; do ((COUNTER++)); echo "Waiting for Redis CRDs to be created, counter: ${COUNTER}"; sleep 5; done echo "Finished waiting for Redis CRDs to be created"' From 067726e3cad558861f69897eb0cec5a8e6b14b49 Mon Sep 17 00:00:00 2001 From: randv1r Date: Sun, 3 Apr 2022 13:17:32 +0300 Subject: [PATCH 097/120] updating clusterRole in README.md --- README.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7989ad7..0245e8a 100644 --- a/README.md +++ b/README.md @@ -136,11 +136,20 @@ kind: ClusterRole metadata: name: redis-operator-cluster-role rules: +- apiGroups: ["apiextensions.k8s.io"] + resources: ["customresourcedefinitions"] + verbs: ["get", "create", "list", "patch"] +- apiGroups: ["app.k8s.io"] + resources: ["applications"] + verbs: ["get", "create", "list", "patch"] - apiGroups: ["admissionregistration.k8s.io"] resources: ["validatingwebhookconfigurations"] verbs: ["*"] -- apiGroups: ["apiextensions.k8s.io"] - resources: ["customresourcedefinitions"] +- apiGroups: ["apps"] + resources: ["deployments"] + verbs: ["get", "create", "list", "patch"] +- apiGroups: [""] + resources: ["secrets", "services"] verbs: ["get", "create", "list", "patch"] - apiGroups: ["app.redislabs.com", "apiextensions.k8s.io"] resources: ["*"] From 853acd8971b57137f6944013a2451ead742ae50a Mon Sep 17 00:00:00 2001 From: randv1r Date: Wed, 6 Apr 2022 19:06:00 +0300 Subject: [PATCH 098/120] RED-65561 - set repo in makefile to production repo --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 56c45cb..c6f2aa8 100644 --- a/Makefile +++ b/Makefile @@ -8,9 +8,9 @@ include ../crd.Makefile # Artifact repo #REGISTRY := us-central1-docker.pkg.dev/proven-reality-226706/redis-market-place # the repo the publish copies from? -#REGISTRY := gcr.io/proven-reality-226706/redislabs +REGISTRY := gcr.io/proven-reality-226706/redislabs # CI registry -REGISTRY ?= gcr.io/redislabs-k8s-dev-238506/gkemp-redis-ci +# REGISTRY ?= gcr.io/redislabs-k8s-dev-238506/gkemp-redis-ci # gcloud.Makefile provides default values for REGISTRY and NAMESPACE derived from local # gcloud and kubectl environments. include ../gcloud.Makefile From 5136f022be33a1707098808e0d6cc979b121af87 Mon Sep 17 00:00:00 2001 From: randv1r Date: Sun, 10 Apr 2022 17:59:36 +0300 Subject: [PATCH 099/120] RED-65561 - adding permissions for deploy service account job mpdev verify failed as a result of the deploy service account job not having right permissions for creating the Redis Enterprise Cluster CR --- schema.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/schema.yaml b/schema.yaml index 323fa78..8ec2d6c 100644 --- a/schema.yaml +++ b/schema.yaml @@ -154,6 +154,9 @@ properties: - apiGroups: [""] resources: ["services", "secrets"] verbs: ["*"] + - apiGroups: ["app.redislabs.com"] + resources: ["redisenterpriseclusters"] + verbs: ["get", "create", "patch"] operator.replicas: type: integer title: Number of Cluster Nodes From cff8aa2e97bfa16a83597c65ab9f058cfecc24d9 Mon Sep 17 00:00:00 2001 From: alon-zada <84660065+alon-zada@users.noreply.github.com> Date: Tue, 21 Jun 2022 10:42:26 +0300 Subject: [PATCH 100/120] RED-78945 - Fix usage meter build and deployer image semver versioning. (#28) --- Makefile | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index c6f2aa8..401917d 100644 --- a/Makefile +++ b/Makefile @@ -31,10 +31,11 @@ $(info ---- OPERATOR_TAG = $(OPERATOR_TAG)) OPERATOR_REPO ?= operator $(info ---- OPERATOR_REPO = $(OPERATOR_REPO)) -# Deployer tag is used for displaying versions in partner portal. -# This version only support major.minor so the Redis version major.minor.patch -# is converted into more readable form of major.2 digit zero padded minor + patch -# without the hyphen +# Deployer tag is used for displaying versions in Producer Portal. +# Producer Portal requires a minor version tag, and it follow the semver.org officially recommended regex: (/^(0|[1-9]\d*)\.(0|[1-9]\d*)$ +# please view: https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string +# the operator tag is converted to the . +# i.e.: 6.2.10-34 is converted to 6.2.1034. # This can also have a different patch number from the OPERATOR_TAG to indicate # a marketplace-only change DEPLOYER_TAG ?= 6.021001 @@ -105,7 +106,7 @@ app/build:: .build/redis-enterprise-operator/deployer \ docker push "$(REGISTRY):$(OPERATOR_TAG)" @touch "$@" -.build/redis-enterprise-operator/usage-meter: usage-meter/**/* \ +.build/redis-enterprise-operator/usage-meter: usage-meter/* \ .build/var/REGISTRY \ .build/var/OPERATOR_TAG \ | .build/redis-enterprise-operator From 36cfff4be2d8943fc16b18a5c7c8e6ed91161d9a Mon Sep 17 00:00:00 2001 From: Yuval Levy Date: Thu, 30 Jun 2022 10:54:19 +0300 Subject: [PATCH 101/120] changes for 6.2.10-45 --- Makefile | 4 +- README.md | 2 +- chart/redis-operator/Chart.yaml | 2 +- deployer/rec_crd.yaml | 156 +++++++++++++++++++++++++++++--- deployer/redb_crd.yaml | 68 +++++++++++++- 5 files changed, 213 insertions(+), 19 deletions(-) diff --git a/Makefile b/Makefile index 401917d..c56a014 100644 --- a/Makefile +++ b/Makefile @@ -21,10 +21,10 @@ $(info ---- REGISTRY = $(REGISTRY)) CHART_NAME := redis-operator $(info ---- CHART_NAME = $(CHART_NAME)) -REDIS_TAG ?= 6.2.10-83 +REDIS_TAG ?= 6.2.10-129 $(info ---- REDIS_TAG = $(REDIS_TAG)) -OPERATOR_TAG ?= 6.2.10-4 +OPERATOR_TAG ?= 6.2.10-45 $(info ---- OPERATOR_TAG = $(OPERATOR_TAG)) # The repo to pull the operator image from Docker Hub registry. diff --git a/README.md b/README.md index 0245e8a..7b4ec70 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ Redis version tags are in the format Major.Minor.Patch-Sub but GKE Marketplace r ```shell export APP_INSTANCE_NAME=redis-enterprise-operator export NAMESPACE=redis -export TAG=6.2.10-4 +export TAG=6.2.10-45 export DEPLOYER_TAG=6.021001 export REPO=gcr.io/cloud-marketplace/redislabs-public/redis-enterprise ``` diff --git a/chart/redis-operator/Chart.yaml b/chart/redis-operator/Chart.yaml index 797502c..f6a758f 100644 --- a/chart/redis-operator/Chart.yaml +++ b/chart/redis-operator/Chart.yaml @@ -1,3 +1,3 @@ apiVersion: "v2" name: redis-operator -version: "1.21" +version: "1.22" diff --git a/deployer/rec_crd.yaml b/deployer/rec_crd.yaml index 8815913..ad4dcac 100644 --- a/deployer/rec_crd.yaml +++ b/deployer/rec_crd.yaml @@ -133,7 +133,9 @@ spec: description: RedisEnterpriseClusterSpec defines the desired state of RedisEnterpriseCluster properties: activeActive: - description: Specification for ActiveActive setup + description: Specification for ActiveActive setup. At most one of + ingressOrRouteSpec or activeActive fields can be set at the same + time. properties: apiIngressUrl: description: RS API URL @@ -179,6 +181,10 @@ spec: type: string versionTag: type: string + digestHash: + description: 'The digest hash of the image to pull. Version tag must be also specified with the corresponding version, will use the digest hash to pull the image if exists. + Note: This is currently supported only for OLM.' + type: string type: object bootstrapperResources: description: Compute resource requirements for bootstrapper containers @@ -207,8 +213,9 @@ spec: type: object type: object clusterCredentialSecretName: - description: Secret Name/Path to use for Cluster Credentials. If left - blank, will use cluster name + description: Secret Name/Path to use for Cluster Credentials. To be + used only if ClusterCredentialSecretType is vault. If left blank, + will use cluster name. type: string clusterCredentialSecretRole: description: Used only if ClusterCredentialSecretType is vault, to define @@ -226,6 +233,16 @@ spec: true. Note that this field is cleared automatically after the cluster is recovered type: boolean + containerTimezone: + description: Container timezone configuration. While the default timezone + on all containers is UTC, this setting can be used to set the timezone + on services rigger/bootstrapper/RS containers. Currently the only + supported value is to propagate the host timezone to all containers. + properties: + propagateHost: + description: Identifies that container timezone should be in sync with the host. + type: object + type: object createServiceAccount: description: Whether to create service account type: boolean @@ -236,9 +253,10 @@ spec: please view the similar setting for REDB for more info. type: boolean encryptPkeys: - description: 'Private key encryption - in order to enable, first need + description: "Private key encryption - in order to enable, first need to mount ${ephemeralconfdir}/secrets/pem/passphrase and add the passphrase - and then set fields value to ''true'' Possible values: true/false' + and then set fields value to ''true'' Possible values: true/false'. Note: this feature + is currently unsupported." type: boolean certificates: description: RS Cluster Certificates. @@ -289,6 +307,42 @@ spec: type: string type: object type: array + ingressOrRouteSpec: + description: Access configurations for the Redis Enterprise Cluster + and Databases. Note - this feature is currently in preview. For + this feature to take effect, set a boolean environment variable + with the name "ENABLE_ALPHA_FEATURES" to True. This variable can + be set via the redis-enterprise-operator pod spec, or through the + operator-environment-config Config Map. At most one of ingressOrRouteSpec + or activeActive fields can be set at the same time. + properties: + apiFqdnUrl: + description: RS API URL + type: string + dbFqdnSuffix: + description: DB ENDPOINT SUFFIX - will be used to set the db host + ingress . Creates a host name so it + should be unique if more than one db is created on the cluster + with the same name + type: string + ingressAnnotations: + additionalProperties: + type: string + description: Additional annotations to set on ingress resources + created by the operator + type: object + method: + description: Used to distinguish between different platforms implementation. + enum: + - openShiftRoute + - ingress + - istio + type: string + required: + - apiFqdnUrl + - dbFqdnSuffix + - method + type: object license: description: Redis Enterprise License type: string @@ -307,7 +361,11 @@ spec: ocspConfiguration: description: An API object that represents the cluster's OCSP configuration. To enable OCSP, the cluster's proxy certificate should contain the - OCSP responder URL. + OCSP responder URL. Note - This is an ALPHA Feature. For this feature + to take effect, set a boolean environment variable with the name + "ENABLE_ALPHA_FEATURES" to True. This variable can be set via the + redis-enterprise-operator pod spec, or through the operator-environment-config + Config Map. properties: ocspFunctionality: description: Whether to enable/disable OCSP mechanism for the @@ -2950,6 +3008,10 @@ spec: type: string versionTag: type: string + digestHash: + description: 'The digest hash of the image to pull. Version tag must be also specified with the corresponding version, will use the digest hash to pull the image if exists. + Note: This is currently supported only for OLM.' + type: string type: object redisEnterpriseNodeResources: description: Compute resource requirements for Redis Enterprise containers @@ -6208,7 +6270,11 @@ spec: type: string redisOnFlashSpec: description: Stores configurations specific to redis on flash. If provided, the cluster will be capable of - creating redis on flash databases + creating redis on flash databases. Note - This is an ALPHA Feature. For this feature + to take effect, set a boolean environment variable with the name + "ENABLE_ALPHA_FEATURES" to True. This variable can be set via the + redis-enterprise-operator pod spec, or through the operator-environment-config + Config Map. properties: enabled: type: boolean @@ -6964,7 +7030,9 @@ spec: description: RedisEnterpriseClusterSpec defines the desired state of RedisEnterpriseCluster properties: activeActive: - description: Specification for ActiveActive setup + description: Specification for ActiveActive setup. At most one of + ingressOrRouteSpec or activeActive fields can be set at the same + time. properties: apiIngressUrl: description: RS API URL @@ -6986,6 +7054,7 @@ spec: enum: - openShiftRoute - ingress + - istio type: string required: - apiIngressUrl @@ -7038,8 +7107,9 @@ spec: type: object type: object clusterCredentialSecretName: - description: Secret Name/Path to use for Cluster Credentials. If left - blank, will use cluster name + description: Secret Name/Path to use for Cluster Credentials. To be + used only if ClusterCredentialSecretType is vault. If left blank, + will use cluster name. type: string clusterCredentialSecretRole: description: Used only if ClusterCredentialSecretType is vault, to define @@ -7057,6 +7127,16 @@ spec: true. Note that this field is cleared automatically after the cluster is recovered type: boolean + containerTimezone: + description: Container timezone configuration. While the default timezone + on all containers is UTC, this setting can be used to set the timezone + on services rigger/bootstrapper/RS containers. Currently the only + supported value is to propagate the host timezone to all containers. + properties: + propagateHost: + description: Identifies that container timezone should be in sync with the host. + type: object + type: object createServiceAccount: description: Whether to create service account type: boolean @@ -7120,6 +7200,42 @@ spec: type: string type: object type: array + ingressOrRouteSpec: + description: Access configurations for the Redis Enterprise Cluster + and Databases. Note - this feature is currently in preview. For + this feature to take effect, set a boolean environment variable + with the name "ENABLE_ALPHA_FEATURES" to True. This variable can + be set via the redis-enterprise-operator pod spec, or through the + operator-environment-config Config Map. At most one of ingressOrRouteSpec + or activeActive fields can be set at the same time. + properties: + apiFqdnUrl: + description: RS API URL + type: string + dbFqdnSuffix: + description: DB ENDPOINT SUFFIX - will be used to set the db host + ingress . Creates a host name so it + should be unique if more than one db is created on the cluster + with the same name + type: string + ingressAnnotations: + additionalProperties: + type: string + description: Additional annotations to set on ingress resources + created by the operator + type: object + method: + description: Used to distinguish between different platforms implementation. + enum: + - openShiftRoute + - ingress + - istio + type: string + required: + - apiFqdnUrl + - dbFqdnSuffix + - method + type: object license: description: Redis Enterprise License type: string @@ -7138,7 +7254,11 @@ spec: ocspConfiguration: description: An API object that represents the cluster's OCSP configuration. To enable OCSP, the cluster's proxy certificate should contain the - OCSP responder URL. + OCSP responder URL. Note - This is an ALPHA Feature. For this feature + to take effect, set a boolean environment variable with the name + "ENABLE_ALPHA_FEATURES" to True. This variable can be set via the + redis-enterprise-operator pod spec, or through the operator-environment-config + Config Map. properties: ocspFunctionality: description: Whether to enable/disable OCSP mechanism for the @@ -9782,6 +9902,10 @@ spec: type: string versionTag: type: string + digestHash: + description: 'The digest hash of the image to pull. Version tag must be also specified with the corresponding version, will use the digest hash to pull the image if exists. + Note: This is currently supported only for OLM.' + type: string type: object redisEnterpriseNodeResources: description: Compute resource requirements for Redis Enterprise containers @@ -9903,6 +10027,10 @@ spec: type: string versionTag: type: string + digestHash: + description: 'The digest hash of the image to pull. Version tag must be also specified with the corresponding version, will use the digest hash to pull the image if exists. + Note: This is currently supported only for OLM.' + type: string type: object redisEnterpriseServicesRiggerResources: description: Compute resource requirements for Services Rigger pod @@ -13040,7 +13168,11 @@ spec: type: string redisOnFlashSpec: description: Stores configurations specific to redis on flash. If provided, the cluster will be capable of - creating redis on flash databases + creating redis on flash databases. Note - This is an ALPHA Feature. For this feature + to take effect, set a boolean environment variable with the name + "ENABLE_ALPHA_FEATURES" to True. This variable can be set via the + redis-enterprise-operator pod spec, or through the operator-environment-config + Config Map. properties: enabled: type: boolean diff --git a/deployer/redb_crd.yaml b/deployer/redb_crd.yaml index cef144d..5a53195 100644 --- a/deployer/redb_crd.yaml +++ b/deployer/redb_crd.yaml @@ -381,9 +381,14 @@ spec: evictionPolicy: description: Database eviction policy. see more https://docs.redislabs.com/latest/rs/administering/database-operations/eviction-policy/ type: string + isRof: + description: Whether it is an RoF database or not. Applicable only for + databases of type "REDIS". Assumed to be false if left blank. + type: boolean memorySize: description: memory size of database. use formats like 100MB, 0.1GB. - minimum value in 100MB. + minimum value in 100MB. When redis on flash (RoF) is enabled, this value refers to RAM+Flash memory, + and it must not be below 1GB. type: string modulesList: description: List of modules associated with database @@ -519,6 +524,55 @@ spec: - enabled - replica_ssl type: string + type: + description: The type of the database (redis or memcached). Defaults to "redis". + enum: + - redis + - memcached + type: string + rofRamSize: + description: The size of the RAM portion of an RoF database. + Similarly to "memorySize" use formats like 100MB, 0.1GB. + It must be at least 10% of combined memory + size (RAM and Flash), as specified by "memorySize". + type: string + redisVersion: + description: Redis OSS version. For existing databases - Upgrade Redis + OSS version. For new databases - the version which the database will + be created with. If set to 'major' - will always upgrade to the most + recent major Redis version. If set to 'latest' - will always upgrade + to the most recent Redis version. Depends on 'redisUpgradePolicy' + - if you want to set the value to 'latest' for some databases, you + must set redisUpgradePolicy on the cluster before. Possible values + are 'major' or 'latest' When using upgrade - make sure to backup the + database before. This value is used only for database type 'redis' + enum: + - major + - latest + type: string + activeActiveName: + description: 'The Redis Enterprise Active Active Peering custom resource name this Resource is associated + with, also, the corresponding active active database name. + In case this resource is created manually + at the active active database creation this field must be filled via the user, + otherwise, the operator will assign this field automatically. + Note: this feature is currently unsupported.' + type: string + globalConfigurations: + description: 'Flag that determines if this is the default global configurations + for active active database. + In case this resource is created manually + at the active active database creation this field must be filled via the user + otherwise the operator will create the global configurations REDB automatically with default values. + Note: this feature is currently unsupported.' + type: boolean + memcachedSaslSecretName: + description: 'Credentials used for binary authentication in memcached databases. + The credentials should be saved as an opaque secret and the name of that secret should be configured using this field. + For username, use ''username'' as the key and the actual username as the value. + For password, use ''password'' as the key and the actual password as the value. + Note that connections are not encrypted.' + type: string type: object status: description: RedisEnterpriseDatabaseStatus defines the observed state of @@ -631,8 +685,6 @@ spec: lastBackupTime: description: Time of last successful backup type: string - required: - - backupHistory type: object specStatus: description: Whether the desired specification is valid @@ -643,5 +695,15 @@ spec: version: description: Database compatibility version type: string + activeActiveName: + description: 'The Redis Enterprise Active Active Peering custom resource this Resource is associated + with, also, the corresponding active active database name. + Note: this feature is currently unsupported.' + type: string + globalConfigurations: + description: 'Flag to determine if this is the default global configurations + for active active database. + Note: this feature is currently unsupported.' + type: boolean type: object type: object From 81e2a291e182b8af3793cc1dbc8184bae46f769c Mon Sep 17 00:00:00 2001 From: Yuval Levy Date: Mon, 4 Jul 2022 17:18:24 +0300 Subject: [PATCH 102/120] usage-meter - Update openssl to resolve vulnerability --- usage-meter/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usage-meter/Dockerfile b/usage-meter/Dockerfile index 87aa8a8..6aa782e 100644 --- a/usage-meter/Dockerfile +++ b/usage-meter/Dockerfile @@ -1,6 +1,6 @@ FROM python:3.8-slim -RUN apt-get update && apt-get install -y curl +RUN apt-get update && apt-get install -y curl && apt-get install -y openssl RUN pip install kubernetes requests RUN mkdir -p /app COPY run.sh /app/ From 74e92612d174208aea2b7bd3678b2c86da815a3b Mon Sep 17 00:00:00 2001 From: Yuval Levy Date: Tue, 20 Sep 2022 10:29:47 +0300 Subject: [PATCH 103/120] 6.2.12-1 promotion --- Makefile | 4 +- README.md | 2 +- chart/redis-operator/Chart.yaml | 2 +- deployer/rec_crd.yaml | 152 ++++++++++++++++++++++++-------- deployer/redb_crd.yaml | 37 +++++--- 5 files changed, 141 insertions(+), 56 deletions(-) diff --git a/Makefile b/Makefile index c56a014..0797bcf 100644 --- a/Makefile +++ b/Makefile @@ -21,10 +21,10 @@ $(info ---- REGISTRY = $(REGISTRY)) CHART_NAME := redis-operator $(info ---- CHART_NAME = $(CHART_NAME)) -REDIS_TAG ?= 6.2.10-129 +REDIS_TAG ?= 6.2.12-82 $(info ---- REDIS_TAG = $(REDIS_TAG)) -OPERATOR_TAG ?= 6.2.10-45 +OPERATOR_TAG ?= 6.2.12-1 $(info ---- OPERATOR_TAG = $(OPERATOR_TAG)) # The repo to pull the operator image from Docker Hub registry. diff --git a/README.md b/README.md index 7b4ec70..37d55c7 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ Redis version tags are in the format Major.Minor.Patch-Sub but GKE Marketplace r ```shell export APP_INSTANCE_NAME=redis-enterprise-operator export NAMESPACE=redis -export TAG=6.2.10-45 +export TAG=6.2.12-1 export DEPLOYER_TAG=6.021001 export REPO=gcr.io/cloud-marketplace/redislabs-public/redis-enterprise ``` diff --git a/chart/redis-operator/Chart.yaml b/chart/redis-operator/Chart.yaml index f6a758f..0b68f26 100644 --- a/chart/redis-operator/Chart.yaml +++ b/chart/redis-operator/Chart.yaml @@ -1,3 +1,3 @@ apiVersion: "v2" name: redis-operator -version: "1.22" +version: "1.23" diff --git a/deployer/rec_crd.yaml b/deployer/rec_crd.yaml index ad4dcac..d79e30e 100644 --- a/deployer/rec_crd.yaml +++ b/deployer/rec_crd.yaml @@ -172,18 +172,23 @@ spec: bootstrapperImageSpec: description: Specification for Bootstrapper container image properties: + digestHash: + description: 'The digest hash of the container image to pull. + When specified, the container image is pulled according to the + digest hash instead of the image tag. The versionTag field must + also be specified with the image tag matching this digest hash. + Note: This field is only supported for OLM deployments.' + type: string imagePullPolicy: - description: PullPolicy describes a policy for if/when to pull a - container image + description: The image pull policy to be applied to the container + image. One of Always, Never, IfNotPresent. type: string repository: - description: Repository + description: The repository (name) of the container image to be + deployed. type: string versionTag: - type: string - digestHash: - description: 'The digest hash of the image to pull. Version tag must be also specified with the corresponding version, will use the digest hash to pull the image if exists. - Note: This is currently supported only for OLM.' + description: The tag of the container image to be deployed. type: string type: object bootstrapperResources: @@ -343,6 +348,17 @@ spec: - dbFqdnSuffix - method type: object + services: + description: Redis-Enterprise-Operator services specifications. + properties: + servicesAnnotations: + additionalProperties: + type: string + description: Global additional annotations to set on service resources + created by the operator. + Note - The specified annotations will not override annotations that already exists and didn't originated from the operator. + type: object + type: object license: description: Redis Enterprise License type: string @@ -556,7 +572,12 @@ spec: type: array type: object podSecurityPolicyName: - description: Name of pod security policy to use on pods See https://kubernetes.io/docs/concepts/policy/pod-security-policy/ + description: "DEPRECATED PodSecurityPolicy support is removed in Kubernetes + v1.25 and the use of this field is invalid for use when running + on Kubernetes v1.25+. Future versions of the RedisEnterpriseCluster + API will remove support for this field altogether. For migration + instructions, see https://kubernetes.io/docs/tasks/configure-pod-container/migrate-from-psp/ + \n Name of pod security policy to use on pods" type: string podStartingPolicy: description: Mitigation setting for STS pods stuck in "ContainerCreating" @@ -2999,18 +3020,23 @@ spec: redisEnterpriseImageSpec: description: Specification for Redis Enterprise container image properties: + digestHash: + description: 'The digest hash of the container image to pull. + When specified, the container image is pulled according to the + digest hash instead of the image tag. The versionTag field must + also be specified with the image tag matching this digest hash. + Note: This field is only supported for OLM deployments.' + type: string imagePullPolicy: - description: PullPolicy describes a policy for if/when to pull a - container image + description: The image pull policy to be applied to the container + image. One of Always, Never, IfNotPresent. type: string repository: - description: Repository + description: The repository (name) of the container image to be + deployed. type: string versionTag: - type: string - digestHash: - description: 'The digest hash of the image to pull. Version tag must be also specified with the corresponding version, will use the digest hash to pull the image if exists. - Note: This is currently supported only for OLM.' + description: The tag of the container image to be deployed. type: string type: object redisEnterpriseNodeResources: @@ -3124,14 +3150,23 @@ spec: redisEnterpriseServicesRiggerImageSpec: description: Specification for Services Rigger container image properties: + digestHash: + description: 'The digest hash of the container image to pull. + When specified, the container image is pulled according to the + digest hash instead of the image tag. The versionTag field must + also be specified with the image tag matching this digest hash. + Note: This field is only supported for OLM deployments.' + type: string imagePullPolicy: - description: PullPolicy describes a policy for if/when to pull a - container image + description: The image pull policy to be applied to the container + image. One of Always, Never, IfNotPresent. type: string repository: - description: Repository + description: The repository (name) of the container image to be + deployed. type: string versionTag: + description: The tag of the container image to be deployed. type: string type: object redisEnterpriseServicesRiggerResources: @@ -6258,7 +6293,10 @@ spec: uiAnnotations: additionalProperties: type: string - description: Annotations for Redis Enterprise UI service + description: Annotations for Redis Enterprise UI service. + This annotations will override the overlapping global annotations set under spec.services.servicesAnnotations + Note - The specified annotations will not override annotations that already exists and didn't originated from the operator + except for the following reserved annotation name redis.io/last-keys. type: object uiServiceType: description: Type of service used to expose Redis Enterprise UI (https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types) @@ -7070,14 +7108,23 @@ spec: bootstrapperImageSpec: description: Specification for Bootstrapper container image properties: + digestHash: + description: 'The digest hash of the container image to pull. + When specified, the container image is pulled according to the + digest hash instead of the image tag. The versionTag field must + also be specified with the image tag matching this digest hash. + Note: This field is only supported for OLM deployments.' + type: string imagePullPolicy: - description: PullPolicy describes a policy for if/when to pull a - container image + description: The image pull policy to be applied to the container + image. One of Always, Never, IfNotPresent. type: string repository: - description: Repository + description: The repository (name) of the container image to be + deployed. type: string versionTag: + description: The tag of the container image to be deployed. type: string type: object bootstrapperResources: @@ -7236,6 +7283,17 @@ spec: - dbFqdnSuffix - method type: object + services: + description: Redis-Enterprise-Operator services specifications. + properties: + servicesAnnotations: + additionalProperties: + type: string + description: Global additional annotations to set on service resources + created by the operator. + Note - The specified annotations will not override annotations that already exists and didn't originated from the operator. + type: object + type: object license: description: Redis Enterprise License type: string @@ -7449,7 +7507,12 @@ spec: type: array type: object podSecurityPolicyName: - description: Name of pod security policy to use on pods See https://kubernetes.io/docs/concepts/policy/pod-security-policy/ + description: "DEPRECATED PodSecurityPolicy support is removed in Kubernetes + v1.25 and the use of this field is invalid for use when running + on Kubernetes v1.25+. Future versions of the RedisEnterpriseCluster + API will remove support for this field altogether. For migration + instructions, see https://kubernetes.io/docs/tasks/configure-pod-container/migrate-from-psp/ + \n Name of pod security policy to use on pods" type: string podStartingPolicy: description: Mitigation setting for STS pods stuck in "ContainerCreating" @@ -9893,18 +9956,23 @@ spec: redisEnterpriseImageSpec: description: Specification for Redis Enterprise container image properties: + digestHash: + description: 'The digest hash of the container image to pull. + When specified, the container image is pulled according to the + digest hash instead of the image tag. The versionTag field must + also be specified with the image tag matching this digest hash. + Note: This field is only supported for OLM deployments.' + type: string imagePullPolicy: - description: PullPolicy describes a policy for if/when to pull a - container image + description: The image pull policy to be applied to the container + image. One of Always, Never, IfNotPresent. type: string repository: - description: Repository + description: The repository (name) of the container image to be + deployed. type: string versionTag: - type: string - digestHash: - description: 'The digest hash of the image to pull. Version tag must be also specified with the corresponding version, will use the digest hash to pull the image if exists. - Note: This is currently supported only for OLM.' + description: The tag of the container image to be deployed. type: string type: object redisEnterpriseNodeResources: @@ -10018,18 +10086,23 @@ spec: redisEnterpriseServicesRiggerImageSpec: description: Specification for Services Rigger container image properties: + digestHash: + description: 'The digest hash of the container image to pull. + When specified, the container image is pulled according to the + digest hash instead of the image tag. The versionTag field must + also be specified with the image tag matching this digest hash. + Note: This field is only supported for OLM deployments.' + type: string imagePullPolicy: - description: PullPolicy describes a policy for if/when to pull a - container image + description: The image pull policy to be applied to the container + image. One of Always, Never, IfNotPresent. type: string repository: - description: Repository + description: The repository (name) of the container image to be + deployed. type: string versionTag: - type: string - digestHash: - description: 'The digest hash of the image to pull. Version tag must be also specified with the corresponding version, will use the digest hash to pull the image if exists. - Note: This is currently supported only for OLM.' + description: The tag of the container image to be deployed. type: string type: object redisEnterpriseServicesRiggerResources: @@ -13156,7 +13229,10 @@ spec: uiAnnotations: additionalProperties: type: string - description: Annotations for Redis Enterprise UI service + description: Annotations for Redis Enterprise UI service. + This annotations will override the overlapping global annotations set under spec.services.servicesAnnotations + Note - The specified annotations will not override annotations that already exists and didn't originated from the operator + except for the following reserved annotation name redis.io/last-keys. type: object uiServiceType: description: Type of service used to expose Redis Enterprise UI (https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types) diff --git a/deployer/redb_crd.yaml b/deployer/redb_crd.yaml index 5a53195..f129f03 100644 --- a/deployer/redb_crd.yaml +++ b/deployer/redb_crd.yaml @@ -449,30 +449,39 @@ spec: items: properties: clientKeySecret: - description: Secret that defines what client key to use. The - secret needs 2 keys in it's map, "cert" that is the PEM encoded - certificate and "key" that is the PEM encoded private key + description: 'Secret that defines the client certificate and + key used by the syncer in the target database cluster. The + secret must have 2 keys in its map: "cert" which is the PEM + encoded certificate, and "key" which is the PEM encoded private + key.' type: string compression: - description: GZIP Compression level (0-9) to use for replication + description: GZIP compression level (0-6) to use for replication. type: integer replicaSourceName: - description: Kubernetes resource name of type ReplicaSourceType + description: The name of the resource from which the source + database URI is derived. The type of resource must match the + type specified in the ReplicaSourceType field. type: string replicaSourceType: - description: Determines what Kuberetes resource ReplicaSourceName - refers to SECRET - Get URI from secret named in ReplicaSourceName. The - secret will have a uri key that defines the complete, redis:// - URI REDB - Determine URI from Kubernetes REDB resource named - in ReplicaSourceName + description: The type of resource from which the source database + URI is derived. If set to 'SECRET', the source database URI + is derived from the secret named in the ReplicaSourceName + field. The secret must have a key named 'uri' that defines + the URI of the source database in the form of 'redis://...'. + The type of secret (kubernetes, vault, ...) is determined + by the secret mechanism used by the underlying REC object. + If set to 'REDB', the source database URI is derived from + the RedisEnterpriseDatabase resource named in the ReplicaSourceName + field. type: string serverCertSecret: - description: Secret that defines the Server's certificate. The - secret needs 1 key in it's map, "cert" that is the PEM encoded - certificate + description: 'Secret that defines the server certificate used + by the proxy in the source database cluster. The secret must + have 1 key in its map: "cert" which is the PEM encoded certificate.' type: string tlsSniName: - description: TLS SNI Name to use + description: TLS SNI name to use for the replication link. type: string required: - replicaSourceName From 84352eb259889cb73809b93d9da890841b015b6f Mon Sep 17 00:00:00 2001 From: Heinrich Koutcherouk Date: Mon, 14 Nov 2022 12:55:32 +0200 Subject: [PATCH 104/120] promote 6.2.18-3 --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 0797bcf..1062142 100644 --- a/Makefile +++ b/Makefile @@ -21,10 +21,10 @@ $(info ---- REGISTRY = $(REGISTRY)) CHART_NAME := redis-operator $(info ---- CHART_NAME = $(CHART_NAME)) -REDIS_TAG ?= 6.2.12-82 +REDIS_TAG ?= 6.2.18-58 $(info ---- REDIS_TAG = $(REDIS_TAG)) -OPERATOR_TAG ?= 6.2.12-1 +OPERATOR_TAG ?= 6.2.18-3 $(info ---- OPERATOR_TAG = $(OPERATOR_TAG)) # The repo to pull the operator image from Docker Hub registry. From 32f2bbc0515a31d5d669d236a092894985446c05 Mon Sep 17 00:00:00 2001 From: Heinrich Koutcherouk Date: Mon, 14 Nov 2022 13:03:40 +0200 Subject: [PATCH 105/120] Revert "promote 6.2.18-3" This reverts commit 84352eb259889cb73809b93d9da890841b015b6f. --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 1062142..0797bcf 100644 --- a/Makefile +++ b/Makefile @@ -21,10 +21,10 @@ $(info ---- REGISTRY = $(REGISTRY)) CHART_NAME := redis-operator $(info ---- CHART_NAME = $(CHART_NAME)) -REDIS_TAG ?= 6.2.18-58 +REDIS_TAG ?= 6.2.12-82 $(info ---- REDIS_TAG = $(REDIS_TAG)) -OPERATOR_TAG ?= 6.2.18-3 +OPERATOR_TAG ?= 6.2.12-1 $(info ---- OPERATOR_TAG = $(OPERATOR_TAG)) # The repo to pull the operator image from Docker Hub registry. From e884500433ed5672920da065e7110badb7f6ce55 Mon Sep 17 00:00:00 2001 From: Heinrich Koutcherouk Date: Mon, 14 Nov 2022 13:07:27 +0200 Subject: [PATCH 106/120] Promote 6.2.18-3 --- Makefile | 4 +- README.md | 2 +- chart/redis-operator/Chart.yaml | 2 +- deployer/rec_crd.yaml | 91 ++++++++++++++++++--------------- 4 files changed, 55 insertions(+), 44 deletions(-) diff --git a/Makefile b/Makefile index 0797bcf..1062142 100644 --- a/Makefile +++ b/Makefile @@ -21,10 +21,10 @@ $(info ---- REGISTRY = $(REGISTRY)) CHART_NAME := redis-operator $(info ---- CHART_NAME = $(CHART_NAME)) -REDIS_TAG ?= 6.2.12-82 +REDIS_TAG ?= 6.2.18-58 $(info ---- REDIS_TAG = $(REDIS_TAG)) -OPERATOR_TAG ?= 6.2.12-1 +OPERATOR_TAG ?= 6.2.18-3 $(info ---- OPERATOR_TAG = $(OPERATOR_TAG)) # The repo to pull the operator image from Docker Hub registry. diff --git a/README.md b/README.md index 37d55c7..02efcc8 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ Redis version tags are in the format Major.Minor.Patch-Sub but GKE Marketplace r ```shell export APP_INSTANCE_NAME=redis-enterprise-operator export NAMESPACE=redis -export TAG=6.2.12-1 +export TAG=6.2.18-3 export DEPLOYER_TAG=6.021001 export REPO=gcr.io/cloud-marketplace/redislabs-public/redis-enterprise ``` diff --git a/chart/redis-operator/Chart.yaml b/chart/redis-operator/Chart.yaml index 0b68f26..db84a07 100644 --- a/chart/redis-operator/Chart.yaml +++ b/chart/redis-operator/Chart.yaml @@ -1,3 +1,3 @@ apiVersion: "v2" name: redis-operator -version: "1.23" +version: "1.24" diff --git a/deployer/rec_crd.yaml b/deployer/rec_crd.yaml index d79e30e..c7ae595 100644 --- a/deployer/rec_crd.yaml +++ b/deployer/rec_crd.yaml @@ -2,6 +2,8 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: redisenterpriseclusters.app.redislabs.com + labels: + app: redis-enterprise spec: group: app.redislabs.com names: @@ -258,11 +260,8 @@ spec: please view the similar setting for REDB for more info. type: boolean encryptPkeys: - description: "Private key encryption - in order to enable, first need - to mount ${ephemeralconfdir}/secrets/pem/passphrase and add the passphrase - and then set fields value to ''true'' Possible values: true/false'. Note: this feature - is currently unsupported." - type: boolean + description: 'Private key encryption Possible values: true/false' + type: boolean certificates: description: RS Cluster Certificates. Used to modify the certificates used by the cluster. @@ -349,14 +348,27 @@ spec: - method type: object services: - description: Redis-Enterprise-Operator services specifications. + description: Customization options for operator-managed service resources + created for Redis Enterprise clusters and databases properties: + apiService: + description: Customization options for the REC API service. + properties: + type: + description: Type of service to create for the REC API service. + Defaults to ClusterIP service, if not specified otherwise. + enum: + - ClusterIP + - NodePort + - LoadBalancer + type: string + type: object servicesAnnotations: additionalProperties: type: string description: Global additional annotations to set on service resources created by the operator. - Note - The specified annotations will not override annotations that already exists and didn't originated from the operator. + The specified annotations will not override annotations that already exist and didn't originate from the operator. type: object type: object license: @@ -377,33 +389,27 @@ spec: ocspConfiguration: description: An API object that represents the cluster's OCSP configuration. To enable OCSP, the cluster's proxy certificate should contain the - OCSP responder URL. Note - This is an ALPHA Feature. For this feature - to take effect, set a boolean environment variable with the name - "ENABLE_ALPHA_FEATURES" to True. This variable can be set via the - redis-enterprise-operator pod spec, or through the operator-environment-config - Config Map. + OCSP responder URL. properties: ocspFunctionality: - description: Whether to enable/disable OCSP mechanism for the - cluster. + description: Whether to enable/disable OCSP mechanism for the cluster. type: boolean queryFrequency: - description: Determines the interval (in seconds) in which the - control plane will poll the OCSP responder for a new status - for the server certificate. Minimum value is 60. Maximum value - is 86400. + description: Determines the interval (in seconds) in which the control + plane will poll the OCSP responder for a new status for the server + certificate. Minimum value is 60. Maximum value is 86400. type: integer recoveryFrequency: - description: Determines the interval (in seconds) in which the - control plane will poll the OCSP responder for a new status - for the server certificate when the current staple is invalid. - Minimum value is 60. Maximum value is 86400. + description: Determines the interval (in seconds) in which the control + plane will poll the OCSP responder for a new status for the server + certificate when the current staple is invalid. Minimum value + is 60. Maximum value is 86400. type: integer recoveryMaxTries: description: Determines the maximum number for the OCSP recovery attempts. After max number of tries passed, the control plane - will revert back to the regular frequency. Minimum value is - 1. Maximum value is 100. + will revert back to the regular frequency. Minimum value is 1. + Maximum value is 100. type: integer responseTimeout: description: Determines the time interval (in seconds) for which @@ -6295,8 +6301,8 @@ spec: type: string description: Annotations for Redis Enterprise UI service. This annotations will override the overlapping global annotations set under spec.services.servicesAnnotations - Note - The specified annotations will not override annotations that already exists and didn't originated from the operator - except for the following reserved annotation name redis.io/last-keys. + The specified annotations will not override annotations that already exist and didn't originate from the operator, + except for the 'redis.io/last-keys' annotation which is reserved. type: object uiServiceType: description: Type of service used to expose Redis Enterprise UI (https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types) @@ -6308,11 +6314,7 @@ spec: type: string redisOnFlashSpec: description: Stores configurations specific to redis on flash. If provided, the cluster will be capable of - creating redis on flash databases. Note - This is an ALPHA Feature. For this feature - to take effect, set a boolean environment variable with the name - "ENABLE_ALPHA_FEATURES" to True. This variable can be set via the - redis-enterprise-operator pod spec, or through the operator-environment-config - Config Map. + creating redis on flash databases. properties: enabled: type: boolean @@ -7284,14 +7286,27 @@ spec: - method type: object services: - description: Redis-Enterprise-Operator services specifications. + description: Customization options for operator-managed service resources + created for Redis Enterprise clusters and databases properties: + apiService: + description: Customization options for the REC API service. + properties: + type: + description: Type of service to create for the REC API service. + Defaults to ClusterIP service, if not specified otherwise. + enum: + - ClusterIP + - NodePort + - LoadBalancer + type: string + type: object servicesAnnotations: additionalProperties: type: string description: Global additional annotations to set on service resources created by the operator. - Note - The specified annotations will not override annotations that already exists and didn't originated from the operator. + The specified annotations will not override annotations that already exist and didn't originate from the operator. type: object type: object license: @@ -13231,8 +13246,8 @@ spec: type: string description: Annotations for Redis Enterprise UI service. This annotations will override the overlapping global annotations set under spec.services.servicesAnnotations - Note - The specified annotations will not override annotations that already exists and didn't originated from the operator - except for the following reserved annotation name redis.io/last-keys. + The specified annotations will not override annotations that already exist and didn't originate from the operator, + except for the 'redis.io/last-keys' annotation which is reserved. type: object uiServiceType: description: Type of service used to expose Redis Enterprise UI (https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types) @@ -13244,11 +13259,7 @@ spec: type: string redisOnFlashSpec: description: Stores configurations specific to redis on flash. If provided, the cluster will be capable of - creating redis on flash databases. Note - This is an ALPHA Feature. For this feature - to take effect, set a boolean environment variable with the name - "ENABLE_ALPHA_FEATURES" to True. This variable can be set via the - redis-enterprise-operator pod spec, or through the operator-environment-config - Config Map. + creating redis on flash databases. properties: enabled: type: boolean From 7805207ccf86ee905bba8887e39222563dc6322c Mon Sep 17 00:00:00 2001 From: almoggue <87272937+almoggue@users.noreply.github.com> Date: Tue, 29 Nov 2022 17:16:49 +0200 Subject: [PATCH 107/120] RED-88872 - operator yaml diff (#33) --- .../templates/deployment/operator.yaml | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/chart/redis-operator/templates/deployment/operator.yaml b/chart/redis-operator/templates/deployment/operator.yaml index 8a74cc5..0630c3c 100644 --- a/chart/redis-operator/templates/deployment/operator.yaml +++ b/chart/redis-operator/templates/deployment/operator.yaml @@ -2,6 +2,8 @@ apiVersion: apps/v1 kind: Deployment metadata: name: redis-enterprise-operator + labels: + app: redis-enterprise namespace: {{ .Release.Namespace }} spec: replicas: 1 @@ -12,6 +14,7 @@ spec: metadata: labels: name: redis-enterprise-operator + app: redis-enterprise spec: serviceAccountName: {{ .Values.operator.serviceAccountName }} initContainers: @@ -53,8 +56,8 @@ spec: livenessProbe: failureThreshold: 3 successThreshold: 1 - periodSeconds: 5 - timeoutSeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 httpGet: path: /healthz port: 8080 @@ -69,12 +72,16 @@ spec: name: operator-environment-config optional: true ports: - - containerPort: 443 + - containerPort: 8443 env: - name: POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace + - name: WATCH_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace resources: limits: cpu: 1000m @@ -85,8 +92,8 @@ spec: readinessProbe: failureThreshold: 3 successThreshold: 1 - periodSeconds: 5 - timeoutSeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 httpGet: path: /healthz port: 8443 @@ -94,8 +101,9 @@ spec: livenessProbe: failureThreshold: 3 successThreshold: 1 - periodSeconds: 5 - timeoutSeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + initialDelaySeconds: 15 httpGet: path: /liveness port: 8443 From 44d6f9309d64235e6950b2595d97631cc731e7d4 Mon Sep 17 00:00:00 2001 From: alon-zada <84660065+alon-zada@users.noreply.github.com> Date: Sun, 19 Mar 2023 21:36:31 +0200 Subject: [PATCH 108/120] Release version 6.4.2-4 (#35) Co-authored-by: Ubuntu --- .gitignore | 4 +- Makefile | 4 +- README.md | 27 +- chart/redis-operator/Chart.yaml | 2 +- .../redis-operator/templates/application.yaml | 2 +- .../templates/job/cr-create.yaml | 11 +- deployer/rec_crd.yaml | 1342 +++++++++++++---- deployer/redb_crd.yaml | 72 +- usage-meter/Dockerfile | 13 +- 9 files changed, 1109 insertions(+), 368 deletions(-) diff --git a/.gitignore b/.gitignore index bc2a983..329f3e5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1 @@ -.build/ -.DS_Store -.idea/ \ No newline at end of file +.build/ \ No newline at end of file diff --git a/Makefile b/Makefile index 1062142..8766d44 100644 --- a/Makefile +++ b/Makefile @@ -21,10 +21,10 @@ $(info ---- REGISTRY = $(REGISTRY)) CHART_NAME := redis-operator $(info ---- CHART_NAME = $(CHART_NAME)) -REDIS_TAG ?= 6.2.18-58 +REDIS_TAG ?= 6.4.2-43 $(info ---- REDIS_TAG = $(REDIS_TAG)) -OPERATOR_TAG ?= 6.2.18-3 +OPERATOR_TAG ?= 6.4.2-4 $(info ---- OPERATOR_TAG = $(OPERATOR_TAG)) # The repo to pull the operator image from Docker Hub registry. diff --git a/README.md b/README.md index 02efcc8..7a12905 100644 --- a/README.md +++ b/README.md @@ -88,8 +88,8 @@ Redis version tags are in the format Major.Minor.Patch-Sub but GKE Marketplace r ```shell export APP_INSTANCE_NAME=redis-enterprise-operator export NAMESPACE=redis -export TAG=6.2.18-3 -export DEPLOYER_TAG=6.021001 +export TAG=6.4.2-4 +export DEPLOYER_TAG=6.424 export REPO=gcr.io/cloud-marketplace/redislabs-public/redis-enterprise ``` @@ -187,7 +187,7 @@ GKE marketplace integration uses Application resource to make easier to manage R kubectl apply -f https://raw.githubusercontent.com/GoogleCloudPlatform/marketplace-k8s-app-tools/master/crd/app-crd.yaml ``` -### Generate bundle from helm +### [Generate bundle from helm](#helm-template-cmd) Run the helm template to generate operator deployment. @@ -250,6 +250,27 @@ kubectl get services -n $NAMESPACE 1. It might take some time for the external IP to be provisioned. 2. This works out-of-the-box in GKE but not in Anthos, where special measures are needed to configure the Load Balancer. +### Upgrade ### +In order to upgrade the operator, recreate the bundle.yaml using the helm [command](#helm-template-cmd).
+Make sure these params are updated with the new version: +1. `deployerHelm.image` +2. `operator.image.tag` +3. `usagemeter.tag` + +Apply the generated yaml.
+You should expect these operator deployment containers to be updated: +1. redis-enterprise-operator +2. admission +3. usage-meter + +In order to upgrade the REC, edit the spec field: +``` +redisEnterpriseImageSpec: + versionTag: +``` + +The redis enterprise STS and the services-rigger will restart with the new version. + # Uninstall the Application diff --git a/chart/redis-operator/Chart.yaml b/chart/redis-operator/Chart.yaml index db84a07..ce0c1e8 100644 --- a/chart/redis-operator/Chart.yaml +++ b/chart/redis-operator/Chart.yaml @@ -1,3 +1,3 @@ apiVersion: "v2" name: redis-operator -version: "1.24" +version: "1.25" diff --git a/chart/redis-operator/templates/application.yaml b/chart/redis-operator/templates/application.yaml index 5b60aad..16696cc 100644 --- a/chart/redis-operator/templates/application.yaml +++ b/chart/redis-operator/templates/application.yaml @@ -26,7 +26,7 @@ spec: #### Before deleting the Application, make sure to delete any Custom Resources from the K8s cluster: ##### 1. first any REDB resources ##### 2. followed by deleting the `rec` (instance of REC) - ##### 3. then delete the ValidatingWebhookConfiguration named `redb.admission.redis.com` + ##### 3. then delete the ValidatingWebhookConfiguration named `redisenterprise.admission.redis.com` ##### 4. only then the Application can be safely Deleted selector: matchLabels: diff --git a/chart/redis-operator/templates/job/cr-create.yaml b/chart/redis-operator/templates/job/cr-create.yaml index 984bed4..43fd325 100644 --- a/chart/redis-operator/templates/job/cr-create.yaml +++ b/chart/redis-operator/templates/job/cr-create.yaml @@ -23,15 +23,18 @@ spec: until kubectl get secret admission-tls; do echo "Waiting for admission-tls secret"; sleep 5; done + kubectl delete ValidatingWebhookConfiguration redb.admission.redis.com 2> /dev/null \ + && echo "Deleted redb.admission.redis.com" \ + || echo "No redb.admission.redis.com to deleted" echo "found admission-tls secret" CERT=$(kubectl get secret admission-tls -o jsonpath='{.data.cert}') kubectl apply -f - << EOF apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration metadata: - name: redb.admission.redis.com + name: redisenterprise.admission.redis.com webhooks: - - name: redb.admission.redis.com + - name: redisenterprise.admission.redis.com failurePolicy: Fail matchPolicy: Exact sideEffects: None @@ -39,8 +42,8 @@ spec: rules: - apiGroups: ["app.redislabs.com"] apiVersions: ["v1alpha1"] - operations: ["*"] - resources: ["redisenterprisedatabases"] + operations: ["CREATE", "UPDATE"] + resources: ["redisenterprisedatabases", "redisenterpriseactiveactivedatabases", "redisenterpriseremoteclusters"] clientConfig: service: namespace: {{ .Release.Namespace }} diff --git a/deployer/rec_crd.yaml b/deployer/rec_crd.yaml index c7ae595..76f3c89 100644 --- a/deployer/rec_crd.yaml +++ b/deployer/rec_crd.yaml @@ -131,6 +131,20 @@ spec: - version type: object type: array + managedAPIs: + description: Indicates cluster APIs that are being managed by the + operator. This only applies to cluster APIs which are optionally-managed + by the operator, such as cluster LDAP configuration. Most other + APIs are automatically managed by the operator, and are not listed + here. + properties: + ldap: + description: Indicate whether cluster LDAP configuration is managed + by the operator. When this is enabled, the operator will reconcile + the cluster LDAP configuration according to the '.spec.ldap' + field in the RedisEnterpriseCluster resource. + type: boolean + type: object spec: description: RedisEnterpriseClusterSpec defines the desired state of RedisEnterpriseCluster properties: @@ -196,6 +210,17 @@ spec: bootstrapperResources: description: Compute resource requirements for bootstrapper containers properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: set limits: additionalProperties: anyOf: @@ -268,24 +293,28 @@ spec: See the "RSClusterCertificates" struct described above to see the supported certificates. properties: apiCertificateSecretName: - description: Secret Name/Path to use for Cluster's API Certificate. - If left blank, will use certificate provided by the cluster. + description: Secret name to use for cluster's API certificate. + If left blank, a cluster-provided certificate will be used. type: string cmCertificateSecretName: - description: Secret Name/Path to use for Cluster's CM Certificate. - If left blank, will use certificate provided by the cluster. + description: Secret name to use for cluster's CM (Cluster Manager) certificate. + If left blank, a cluster-provided certificate will be used. type: string metricsExporterCertificateSecretName: - description: Secret Name/Path to use for Cluster's Metrics Exporter Certificate. - If left blank, will use certificate provided by the cluster. + description: Secret name to use for cluster's Metrics Exporter certificate. + If left blank, a cluster-provided certificate will be used. type: string proxyCertificateSecretName: - description: Secret Name/Path to use for Cluster's Proxy Certificate. - If left blank, will use certificate provided by the cluster. + description: Secret name to use for cluster's Proxy certificate. + If left blank, a cluster-provided certificate will be used. type: string syncerCertificateSecretName: - description: Secret Name/Path to use for Cluster's Syncer Certificate. - If left blank, will use certificate provided by the cluster. + description: Secret name to use for cluster's Syncer certificate. + If left blank, a cluster-provided certificate will be used. + type: string + ldapClientCertificateSecretName: + description: Secret name to use for cluster's LDAP client certificate. + If left blank, LDAP client certificate authentication will be disabled. type: string type: object enforceIPv4: @@ -371,6 +400,155 @@ spec: The specified annotations will not override annotations that already exist and didn't originate from the operator. type: object type: object + ldap: + description: Cluster-level LDAP configuration, such as server addresses, + protocol, authentication and query settings. + properties: + authenticationQuery: + description: Configuration of authentication queries, mapping + between the username, provided to the cluster for authentication, + and the LDAP Distinguished Name. + properties: + query: + description: Configuration for a search query. Mutually exclusive + with the 'template' field. The substring '%u' in the query + filter will be replaced with the username. + properties: + base: + description: The Distinguished Name of the entry at which + to start the search, e.g., 'ou=dev,dc=example,dc=com'. + type: string + filter: + description: An RFC-4515 string representation of the + filter to apply in the search. For an authentication + query, the substring '%u' will be replaced with the + username, e.g., '(cn=%u)'. For an authorization query, + the substring '%D' will be replaced with the user's + Distinguished Name, e.g., '(members=%D)'. + type: string + scope: + description: 'The search scope for an LDAP query. One + of: BaseObject, SingleLevel, WholeSubtree' + enum: + - BaseObject + - SingleLevel + - WholeSubtree + type: string + required: + - base + - filter + - scope + type: object + template: + description: Configuration for a template query. Mutually + exclusive with the 'query' field. The substring '%u' will + be replaced with the username, e.g., 'cn=%u,ou=dev,dc=example,dc=com'. + type: string + type: object + authorizationQuery: + description: Configuration of authorization queries, mapping between + a user's Distinguished Name and its group memberships. + properties: + attribute: + description: Configuration for an attribute query. Mutually + exclusive with the 'query' field. Holds the name of an attribute + of the LDAP user entity that contains a list of the groups + that the user belongs to, e.g., 'memberOf'. + type: string + query: + description: Configuration for a search query. Mutually exclusive + with the 'attribute' field. The substring '%D' in the query + filter will be replaced with the user's Distinguished Name. + properties: + base: + description: The Distinguished Name of the entry at which + to start the search, e.g., 'ou=dev,dc=example,dc=com'. + type: string + filter: + description: An RFC-4515 string representation of the + filter to apply in the search. For an authentication + query, the substring '%u' will be replaced with the + username, e.g., '(cn=%u)'. For an authorization query, + the substring '%D' will be replaced with the user's + Distinguished Name, e.g., '(members=%D)'. + type: string + scope: + description: 'The search scope for an LDAP query. One + of: BaseObject, SingleLevel, WholeSubtree' + enum: + - BaseObject + - SingleLevel + - WholeSubtree + type: string + required: + - base + - filter + - scope + type: object + type: object + bindCredentialsSecretName: + description: Name of a secret within the same namespace, holding + the credentials used to communicate with the LDAP server for + authentication queries. The secret must have a key named 'dn' + with the Distinguished Name of the user to execute the query, + and 'password' with its password. If left blank, credentials-based + authentication is disabled. + type: string + caCertificateSecretName: + description: Name of a secret within the same namespace, holding + a PEM-encoded CA certificate for validating the TLS connection + to the LDAP server. The secret must have a key named 'cert' + with the certificate data. This field is applicable only when + the protocol is LDAPS or STARTTLS. + type: string + cacheTTLSeconds: + default: 300 + description: The maximum TTL of cached entries. + type: integer + enabledForControlPlane: + default: false + description: Whether to enable LDAP for control plane access. + Disabled by default. + type: boolean + enabledForDataPlane: + default: false + description: Whether to enable LDAP for data plane access. Disabled + by default. + type: boolean + protocol: + description: 'Specifies the LDAP protocol to use. One of: LDAP, + LDAPS, STARTTLS.' + enum: + - LDAP + - LDAPS + - STARTTLS + type: string + servers: + description: One or more LDAP servers. If multiple servers are + specified, they must all share an identical organization tree + structure. + items: + description: Address of an LDAP server. + properties: + host: + description: Host name of the LDAP server + type: string + port: + description: Port number of the LDAP server. If unspecified, + defaults to 389 for LDAP and STARTTLS protocols, and 636 + for LDAPS protocol. + format: int32 + type: integer + required: + - host + type: object + type: array + required: + - authenticationQuery + - authorizationQuery + - protocol + - servers + type: object license: description: Redis Enterprise License type: string @@ -1242,6 +1420,16 @@ spec: failureThreshold: format: int32 type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + type: string + required: + - port + type: object httpGet: properties: host: @@ -1332,6 +1520,16 @@ spec: failureThreshold: format: int32 type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + type: string + required: + - port + type: object httpGet: properties: host: @@ -1390,6 +1588,17 @@ spec: type: object resources: properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: set limits: additionalProperties: anyOf: @@ -1480,6 +1689,16 @@ spec: failureThreshold: format: int32 type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + type: string + required: + - port + type: object httpGet: properties: host: @@ -1603,6 +1822,8 @@ spec: type: boolean hostPID: type: boolean + hostUsers: + type: boolean hostname: type: string imagePullSecrets: @@ -1823,6 +2044,16 @@ spec: failureThreshold: format: int32 type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + type: string + required: + - port + type: object httpGet: properties: host: @@ -1917,6 +2148,16 @@ spec: failureThreshold: format: int32 type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + type: string + required: + - port + type: object httpGet: properties: host: @@ -1975,6 +2216,17 @@ spec: type: object resources: properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: set limits: additionalProperties: anyOf: @@ -2065,6 +2317,16 @@ spec: failureThreshold: format: int32 type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + type: string + required: + - port + type: object httpGet: properties: host: @@ -2176,6 +2438,13 @@ spec: type: string type: object x-kubernetes-map-type: atomic + os: + properties: + name: + type: string + required: + - name + type: object overhead: additionalProperties: anyOf: @@ -2200,12 +2469,43 @@ spec: - conditionType type: object type: array + resourceClaims: + items: + properties: + name: + type: string + source: + properties: + resourceClaimName: + type: string + resourceClaimTemplateName: + type: string + type: object + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map restartPolicy: type: string runtimeClassName: type: string schedulerName: type: string + schedulingGates: + items: + properties: + name: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map securityContext: properties: fsGroup: @@ -2325,9 +2625,21 @@ spec: type: string type: object type: object + matchLabelKeys: + items: + type: string + type: array + x-kubernetes-list-type: atomic maxSkew: format: int32 type: integer + minDomains: + format: int32 + type: integer + nodeAffinityPolicy: + type: string + nodeTaintsPolicy: + type: string topologyKey: type: string whenUnsatisfiable: @@ -2557,12 +2869,25 @@ spec: type: string name: type: string + namespace: + type: string required: - kind - name type: object resources: properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: set limits: additionalProperties: anyOf: @@ -3048,6 +3373,17 @@ spec: redisEnterpriseNodeResources: description: Compute resource requirements for Redis Enterprise containers properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: set limits: additionalProperties: anyOf: @@ -3178,6 +3514,17 @@ spec: redisEnterpriseServicesRiggerResources: description: Compute resource requirements for Services Rigger pod properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: set limits: additionalProperties: anyOf: @@ -3945,6 +4292,16 @@ spec: failureThreshold: format: int32 type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + type: string + required: + - port + type: object httpGet: properties: host: @@ -4035,6 +4392,16 @@ spec: failureThreshold: format: int32 type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + type: string + required: + - port + type: object httpGet: properties: host: @@ -4093,6 +4460,17 @@ spec: type: object resources: properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: set limits: additionalProperties: anyOf: @@ -4183,6 +4561,16 @@ spec: failureThreshold: format: int32 type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + type: string + required: + - port + type: object httpGet: properties: host: @@ -4306,6 +4694,8 @@ spec: type: boolean hostPID: type: boolean + hostUsers: + type: boolean hostname: type: string imagePullSecrets: @@ -4526,6 +4916,16 @@ spec: failureThreshold: format: int32 type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + type: string + required: + - port + type: object httpGet: properties: host: @@ -4620,6 +5020,16 @@ spec: failureThreshold: format: int32 type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + type: string + required: + - port + type: object httpGet: properties: host: @@ -4678,6 +5088,17 @@ spec: type: object resources: properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: set limits: additionalProperties: anyOf: @@ -4768,6 +5189,16 @@ spec: failureThreshold: format: int32 type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + type: string + required: + - port + type: object httpGet: properties: host: @@ -4879,6 +5310,13 @@ spec: type: string type: object x-kubernetes-map-type: atomic + os: + properties: + name: + type: string + required: + - name + type: object overhead: additionalProperties: anyOf: @@ -4903,20 +5341,51 @@ spec: - conditionType type: object type: array - restartPolicy: - type: string - runtimeClassName: - type: string - schedulerName: - type: string - securityContext: - properties: - fsGroup: - format: int64 - type: integer - fsGroupChangePolicy: - type: string - runAsGroup: + resourceClaims: + items: + properties: + name: + type: string + source: + properties: + resourceClaimName: + type: string + resourceClaimTemplateName: + type: string + type: object + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + restartPolicy: + type: string + runtimeClassName: + type: string + schedulerName: + type: string + schedulingGates: + items: + properties: + name: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + securityContext: + properties: + fsGroup: + format: int64 + type: integer + fsGroupChangePolicy: + type: string + runAsGroup: format: int64 type: integer runAsNonRoot: @@ -5028,9 +5497,21 @@ spec: type: string type: object type: object + matchLabelKeys: + items: + type: string + type: array + x-kubernetes-list-type: atomic maxSkew: format: int32 type: integer + minDomains: + format: int32 + type: integer + nodeAffinityPolicy: + type: string + nodeTaintsPolicy: + type: string topologyKey: type: string whenUnsatisfiable: @@ -5260,12 +5741,25 @@ spec: type: string name: type: string + namespace: + type: string required: - kind - name type: object resources: properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: set limits: additionalProperties: anyOf: @@ -5938,6 +6432,16 @@ spec: failureThreshold: format: int32 type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + type: string + required: + - port + type: object httpGet: properties: host: @@ -6032,6 +6536,16 @@ spec: failureThreshold: format: int32 type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + type: string + required: + - port + type: object httpGet: properties: host: @@ -6090,6 +6604,17 @@ spec: type: object resources: properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: set limits: additionalProperties: anyOf: @@ -6180,6 +6705,16 @@ spec: failureThreshold: format: int32 type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + type: string + required: + - port + type: object httpGet: properties: host: @@ -6983,8 +7518,6 @@ spec: status: {} schema: openAPIV3Schema: - description: RedisEnterpriseCluster is the Schema for the redisenterpriseclusters - API properties: apiVersion: type: string @@ -7013,30 +7546,18 @@ spec: items: type: string ocspStatus: - description: An API object that represents the cluster's OCSP status properties: certStatus: - description: Indicates the proxy certificate status - GOOD/REVOKED/UNKNOWN. type: string nextUpdate: - description: The time at or before which newer information will - be available about the status of the certificate (if available) type: string producedAt: - description: The time at which the OCSP responder signed this - response. type: string responderUrl: - description: The OCSP responder url from which this status came - from. type: string revocationTime: - description: The time at which the certificate was revoked or - placed on hold. type: string thisUpdate: - description: The most recent time at which the status being indicated - is known by the responder to have been correct. type: string type: object licenseStatus: @@ -7051,10 +7572,6 @@ spec: shardsLimit: type: integer bundledDatabaseVersions: - description: Versions of open source databases bundled by Redis Enterprise - Software - please note that in order to use a specific version it - should be supported by the ‘upgradePolicy’ - ‘major’ or ‘latest’ according - to the desired version (major/minor) items: properties: dbType: @@ -7066,31 +7583,24 @@ spec: - version type: object type: array + managedAPIs: + properties: + ldap: + type: boolean + type: object spec: - description: RedisEnterpriseClusterSpec defines the desired state of RedisEnterpriseCluster properties: activeActive: - description: Specification for ActiveActive setup. At most one of - ingressOrRouteSpec or activeActive fields can be set at the same - time. properties: apiIngressUrl: - description: RS API URL type: string dbIngressSuffix: - description: DB ENDPOINT SUFFIX - will be used to set the db host. - ingress Creates a host name so it - should be unique if more than one db is created on the cluster - with the same name type: string ingressAnnotations: additionalProperties: type: string - description: Used for ingress controllers such as ha-proxy or nginx - in GKE type: object method: - description: Used to distinguish between different platforms implementation enum: - openShiftRoute - ingress @@ -7102,36 +7612,33 @@ spec: - method type: object antiAffinityAdditionalTopologyKeys: - description: Additional antiAffinity terms in order to support installation - on different zones/vcenters items: type: string type: array bootstrapperImageSpec: - description: Specification for Bootstrapper container image properties: digestHash: - description: 'The digest hash of the container image to pull. - When specified, the container image is pulled according to the - digest hash instead of the image tag. The versionTag field must - also be specified with the image tag matching this digest hash. - Note: This field is only supported for OLM deployments.' type: string imagePullPolicy: - description: The image pull policy to be applied to the container - image. One of Always, Never, IfNotPresent. type: string repository: - description: The repository (name) of the container image to be - deployed. type: string versionTag: - description: The tag of the container image to be deployed. type: string type: object bootstrapperResources: - description: Compute resource requirements for bootstrapper containers properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: set limits: additionalProperties: anyOf: @@ -7139,8 +7646,6 @@ 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-compute-resources-container/' type: object requests: additionalProperties: @@ -7149,132 +7654,73 @@ 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-compute-resources-container/' type: object type: object clusterCredentialSecretName: - description: Secret Name/Path to use for Cluster Credentials. To be - used only if ClusterCredentialSecretType is vault. If left blank, - will use cluster name. type: string clusterCredentialSecretRole: - description: Used only if ClusterCredentialSecretType is vault, to define - vault role to be used. If blank, defaults to "redis-enterprise-operator" type: string clusterCredentialSecretType: - description: Type of Secret to use for ClusterCredential, Vault, Kuberetes,... - If left blank, will default ot kubernetes secrets enum: - vault - kubernetes type: string clusterRecovery: - description: ClusterRecovery initiates cluster recovery when set to - true. Note that this field is cleared automatically after the cluster - is recovered type: boolean containerTimezone: - description: Container timezone configuration. While the default timezone - on all containers is UTC, this setting can be used to set the timezone - on services rigger/bootstrapper/RS containers. Currently the only - supported value is to propagate the host timezone to all containers. properties: propagateHost: - description: Identifies that container timezone should be in sync with the host. type: object type: object createServiceAccount: - description: Whether to create service account type: boolean dataInternodeEncryption: - description: Internode encryption (INE) cluster wide policy. An optional boolean setting. - Specifies if INE should be on/off for new created REDBs. - May be overridden for specific REDB via similar setting, - please view the similar setting for REDB for more info. type: boolean encryptPkeys: - description: 'Private key encryption - in order to enable, first need - to mount ${ephemeralconfdir}/secrets/pem/passphrase and add the passphrase - and then set fields value to ''true'' Possible values: true/false' type: boolean certificates: - description: RS Cluster Certificates. - Used to modify the certificates used by the cluster. - See the "RSClusterCertificates" struct described above to see the supported certificates. properties: apiCertificateSecretName: - description: Secret Name/Path to use for Cluster's API Certificate. - If left blank, will use certificate provided by the cluster. type: string cmCertificateSecretName: - description: Secret Name/Path to use for Cluster's CM Certificate. - If left blank, will use certificate provided by the cluster. type: string metricsExporterCertificateSecretName: - description: Secret Name/Path to use for Cluster's Metrics Exporter Certificate. - If left blank, will use certificate provided by the cluster. type: string proxyCertificateSecretName: - description: Secret Name/Path to use for Cluster's Proxy Certificate. - If left blank, will use certificate provided by the cluster. type: string syncerCertificateSecretName: - description: Secret Name/Path to use for Cluster's Syncer Certificate. - If left blank, will use certificate provided by the cluster. + type: string + ldapClientCertificateSecretName: type: string type: object enforceIPv4: - description: Sets ENFORCE_IPV4 environment variable type: boolean extraLabels: additionalProperties: type: string - description: Labels that the user defines for their convenience type: object hostAliases: items: - description: HostAlias holds the mapping between IP and hostnames - that will be injected as an entry in the pod's hosts file. properties: hostnames: - description: Hostnames for the above IP address. items: type: string type: array ip: - description: IP address of the host file entry. type: string type: object type: array ingressOrRouteSpec: - description: Access configurations for the Redis Enterprise Cluster - and Databases. Note - this feature is currently in preview. For - this feature to take effect, set a boolean environment variable - with the name "ENABLE_ALPHA_FEATURES" to True. This variable can - be set via the redis-enterprise-operator pod spec, or through the - operator-environment-config Config Map. At most one of ingressOrRouteSpec - or activeActive fields can be set at the same time. properties: apiFqdnUrl: - description: RS API URL type: string dbFqdnSuffix: - description: DB ENDPOINT SUFFIX - will be used to set the db host - ingress . Creates a host name so it - should be unique if more than one db is created on the cluster - with the same name type: string ingressAnnotations: additionalProperties: type: string - description: Additional annotations to set on ingress resources - created by the operator type: object method: - description: Used to distinguish between different platforms implementation. enum: - openShiftRoute - ingress @@ -7286,15 +7732,10 @@ spec: - method type: object services: - description: Customization options for operator-managed service resources - created for Redis Enterprise clusters and databases properties: apiService: - description: Customization options for the REC API service. properties: type: - description: Type of service to create for the REC API service. - Defaults to ClusterIP service, if not specified otherwise. enum: - ClusterIP - NodePort @@ -7304,77 +7745,120 @@ spec: servicesAnnotations: additionalProperties: type: string - description: Global additional annotations to set on service resources - created by the operator. - The specified annotations will not override annotations that already exist and didn't originate from the operator. type: object type: object + ldap: + properties: + authenticationQuery: + properties: + query: + properties: + base: + type: string + filter: + type: string + scope: + enum: + - BaseObject + - SingleLevel + - WholeSubtree + type: string + required: + - base + - filter + - scope + type: object + template: + type: string + type: object + authorizationQuery: + properties: + attribute: + type: string + query: + properties: + base: + type: string + filter: + type: string + scope: + enum: + - BaseObject + - SingleLevel + - WholeSubtree + type: string + required: + - base + - filter + - scope + type: object + type: object + bindCredentialsSecretName: + type: string + caCertificateSecretName: + type: string + cacheTTLSeconds: + default: 300 + type: integer + enabledForControlPlane: + default: false + type: boolean + enabledForDataPlane: + default: false + type: boolean + protocol: + enum: + - LDAP + - LDAPS + - STARTTLS + type: string + servers: + items: + properties: + host: + type: string + port: + format: int32 + type: integer + required: + - host + type: object + type: array + required: + - authenticationQuery + - authorizationQuery + - protocol + - servers + type: object license: - description: Redis Enterprise License type: string licenseSecretName: - description: K8s secret or Vault Secret Name/Path to use for Cluster - License. When left blank, the license is read from the "license" field. - Note that you can't specify non-empty values in both "license" and - "licenseSecretName", only one of these fields can be used to pass - the license string. The license needs to be stored under the key "license". type: string nodeSelector: additionalProperties: type: string - description: Selector for nodes that could fit Redis Enterprise pod type: object ocspConfiguration: - description: An API object that represents the cluster's OCSP configuration. - To enable OCSP, the cluster's proxy certificate should contain the - OCSP responder URL. Note - This is an ALPHA Feature. For this feature - to take effect, set a boolean environment variable with the name - "ENABLE_ALPHA_FEATURES" to True. This variable can be set via the - redis-enterprise-operator pod spec, or through the operator-environment-config - Config Map. properties: ocspFunctionality: - description: Whether to enable/disable OCSP mechanism for the - cluster. type: boolean queryFrequency: - description: Determines the interval (in seconds) in which the - control plane will poll the OCSP responder for a new status - for the server certificate. Minimum value is 60. Maximum value - is 86400. type: integer recoveryFrequency: - description: Determines the interval (in seconds) in which the - control plane will poll the OCSP responder for a new status - for the server certificate when the current staple is invalid. - Minimum value is 60. Maximum value is 86400. type: integer recoveryMaxTries: - description: Determines the maximum number for the OCSP recovery - attempts. After max number of tries passed, the control plane - will revert back to the regular frequency. Minimum value is - 1. Maximum value is 100. type: integer responseTimeout: - description: Determines the time interval (in seconds) for which - the request waits for a response from the OCSP responder. Minimum - value is 1. Maximum value is 60. type: integer type: object nodes: - description: Number of Redis Enterprise nodes (pods) format: int32 type: integer persistentSpec: - description: Specification for Redis Enterprise Cluster persistence properties: enabled: - description: Whether to add persistent volume to Redis Enterprise - pods type: boolean storageClassName: - description: Storage class for persistent volume in Redis Enterprise - pods Leave empty to use the default type: string volumeSize: anyOf: @@ -7386,12 +7870,8 @@ spec: podAnnotations: additionalProperties: type: string - description: pod annotations type: object podAntiAffinity: - description: 'Override for the default anti-affinity rules of the Redis - Enterprise pods. - More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#an-example-of-a-pod-that-uses-pod-affinity' properties: preferredDuringSchedulingIgnoredDuringExecution: items: @@ -7522,24 +8002,12 @@ spec: type: array type: object podSecurityPolicyName: - description: "DEPRECATED PodSecurityPolicy support is removed in Kubernetes - v1.25 and the use of this field is invalid for use when running - on Kubernetes v1.25+. Future versions of the RedisEnterpriseCluster - API will remove support for this field altogether. For migration - instructions, see https://kubernetes.io/docs/tasks/configure-pod-container/migrate-from-psp/ - \n Name of pod security policy to use on pods" type: string podStartingPolicy: - description: Mitigation setting for STS pods stuck in "ContainerCreating" properties: enabled: - description: Whether to detect and attempt to mitigate pod startup - issues type: boolean startingThresholdSeconds: - description: Time in seconds to wait for a pod to be stuck while - starting up before action is taken. If set to 0, will be treated - as if disabled. format: int32 type: integer required: @@ -7547,8 +8015,6 @@ spec: - startingThresholdSeconds type: object podTolerations: - description: 'Tolerations that are added to all managed pods. More - information: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/' items: properties: effect: @@ -7565,32 +8031,17 @@ spec: type: object type: array priorityClassName: - description: Adds the priority class to pods managed by the operator type: string pullSecrets: - description: 'PullSecrets is an optional list of references to secrets - in the same namespace to use for pulling any of the images. If specified, - these secrets will be passed to individual puller implementations - for them to use. More info: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/' items: properties: name: - description: 'Secret name' type: string type: object type: array rackAwarenessNodeLabel: - description: Node label that specifies rack ID - if specified, will - create rack aware cluster. Rack awareness requires node label must - exist on all nodes. Additionally, operator needs a special cluster - role with permission to list nodes. type: string redisEnterpriseAdditionalPodSpecAttributes: - description: ADVANCED USAGE USE AT YOUR OWN RISK - specify pod attributes - that are required for the statefulset - Redis Enterprise pods. Pod - attributes managed by the operator might override these settings. - Also make sure the attributes are supported by the K8s version running - on the cluster - the operator does not validate that. properties: activeDeadlineSeconds: format: int64 @@ -7951,7 +8402,6 @@ spec: type: object automountServiceAccountToken: type: boolean - dnsConfig: properties: nameservers: @@ -8187,6 +8637,16 @@ spec: failureThreshold: format: int32 type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + type: string + required: + - port + type: object httpGet: properties: host: @@ -8277,6 +8737,16 @@ spec: failureThreshold: format: int32 type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + type: string + required: + - port + type: object httpGet: properties: host: @@ -8335,6 +8805,17 @@ spec: type: object resources: properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: set limits: additionalProperties: anyOf: @@ -8425,6 +8906,16 @@ spec: failureThreshold: format: int32 type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + type: string + required: + - port + type: object httpGet: properties: host: @@ -8548,6 +9039,8 @@ spec: type: boolean hostPID: type: boolean + hostUsers: + type: boolean hostname: type: string imagePullSecrets: @@ -8768,6 +9261,16 @@ spec: failureThreshold: format: int32 type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + type: string + required: + - port + type: object httpGet: properties: host: @@ -8862,6 +9365,16 @@ spec: failureThreshold: format: int32 type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + type: string + required: + - port + type: object httpGet: properties: host: @@ -8920,6 +9433,17 @@ spec: type: object resources: properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: set limits: additionalProperties: anyOf: @@ -9010,6 +9534,16 @@ spec: failureThreshold: format: int32 type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + type: string + required: + - port + type: object httpGet: properties: host: @@ -9121,6 +9655,13 @@ spec: type: string type: object x-kubernetes-map-type: atomic + os: + properties: + name: + type: string + required: + - name + type: object overhead: additionalProperties: anyOf: @@ -9145,12 +9686,43 @@ spec: - conditionType type: object type: array + resourceClaims: + items: + properties: + name: + type: string + source: + properties: + resourceClaimName: + type: string + resourceClaimTemplateName: + type: string + type: object + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map restartPolicy: type: string runtimeClassName: type: string schedulerName: type: string + schedulingGates: + items: + properties: + name: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map securityContext: properties: fsGroup: @@ -9270,9 +9842,21 @@ spec: type: string type: object type: object + matchLabelKeys: + items: + type: string + type: array + x-kubernetes-list-type: atomic maxSkew: format: int32 type: integer + minDomains: + format: int32 + type: integer + nodeAffinityPolicy: + type: string + nodeTaintsPolicy: + type: string topologyKey: type: string whenUnsatisfiable: @@ -9502,12 +10086,25 @@ spec: type: string name: type: string + namespace: + type: string required: - kind - name type: object resources: properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: set limits: additionalProperties: anyOf: @@ -9969,30 +10566,29 @@ spec: type: array type: object redisEnterpriseImageSpec: - description: Specification for Redis Enterprise container image properties: digestHash: - description: 'The digest hash of the container image to pull. - When specified, the container image is pulled according to the - digest hash instead of the image tag. The versionTag field must - also be specified with the image tag matching this digest hash. - Note: This field is only supported for OLM deployments.' type: string imagePullPolicy: - description: The image pull policy to be applied to the container - image. One of Always, Never, IfNotPresent. type: string repository: - description: The repository (name) of the container image to be - deployed. type: string versionTag: - description: The tag of the container image to be deployed. type: string type: object redisEnterpriseNodeResources: - description: Compute resource requirements for Redis Enterprise containers properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: set limits: additionalProperties: anyOf: @@ -10000,8 +10596,6 @@ 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-compute-resources-container/' type: object requests: additionalProperties: @@ -10010,19 +10604,13 @@ 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-compute-resources-container/' type: object type: object redisEnterpriseServicesConfiguration: - description: RS Cluster optional services settings properties: cmServer: properties: operatingMode: - description: Whether to enable/disable the CM server enum: - enabled - disabled @@ -10033,8 +10621,6 @@ spec: crdbCoordinator: properties: operatingMode: - description: Whether to enable/disable the crdb coordinator - process enum: - enabled - disabled @@ -10045,7 +10631,6 @@ spec: crdbWorker: properties: operatingMode: - description: Whether to enable/disable the crdb worker processes enum: - enabled - disabled @@ -10056,7 +10641,6 @@ spec: mdnsServer: properties: operatingMode: - description: Whether to enable/disable the Multicast DNS server enum: - enabled - disabled @@ -10067,7 +10651,6 @@ spec: pdnsServer: properties: operatingMode: - description: Whether to enable/disable the pdns server enum: - enabled - disabled @@ -10078,7 +10661,6 @@ spec: saslauthd: properties: operatingMode: - description: Whether to enable/disable the saslauthd service enum: - enabled - disabled @@ -10089,7 +10671,6 @@ spec: statsArchiver: properties: operatingMode: - description: Whether to enable/disable the stats archiver service enum: - enabled - disabled @@ -10099,30 +10680,29 @@ spec: type: object type: object redisEnterpriseServicesRiggerImageSpec: - description: Specification for Services Rigger container image properties: digestHash: - description: 'The digest hash of the container image to pull. - When specified, the container image is pulled according to the - digest hash instead of the image tag. The versionTag field must - also be specified with the image tag matching this digest hash. - Note: This field is only supported for OLM deployments.' type: string imagePullPolicy: - description: The image pull policy to be applied to the container - image. One of Always, Never, IfNotPresent. type: string repository: - description: The repository (name) of the container image to be - deployed. type: string versionTag: - description: The tag of the container image to be deployed. type: string type: object redisEnterpriseServicesRiggerResources: - description: Compute resource requirements for Services Rigger pod properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: set limits: additionalProperties: anyOf: @@ -10130,8 +10710,6 @@ 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-compute-resources-container/' type: object requests: additionalProperties: @@ -10140,19 +10718,12 @@ 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-compute-resources-container/' type: object type: object redisEnterpriseTerminationGracePeriodSeconds: - description: The TerminationGracePeriodSeconds value for the (STS created) REC pods format: int64 type: integer redisEnterpriseVolumeMounts: - description: 'additional volume mounts within the redis enterprise containers. - More info: https://kubernetes.io/docs/concepts/storage/volumes/' items: properties: mountPath: @@ -10173,106 +10744,67 @@ spec: type: object type: array redisUpgradePolicy: - description: 'Redis upgrade policy to be set on the Redis Enterprise - Cluster. Possible values: major/latest This value is used by the cluster - to choose the Redis version of the database when an upgrade is performed. - The Redis Enterprise Cluster includes multiple versions of OSS Redis - that can be used for databases.' enum: - major - latest type: string serviceAccountName: - description: Name of the service account to use type: string servicesRiggerSpec: - description: Specification for service rigger properties: databaseServiceType: - description: Service types for access to databases. should be a - comma separated list. The possible values are cluster_ip, headless - and load_balancer. type: string extraEnvVars: items: - description: 'EnvVar represents an environment variable present - in a Container. - More info: https://kubernetes.io/docs/tasks/inject-data-application/environment-variable-expose-pod-information/' properties: name: - description: Name of the environment variable. type: string value: 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 type: string optional: - description: Specify whether the ConfigMap or its - key must be defined type: boolean required: - key type: object fieldRef: - description: Selects a field of the pod 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 resourceFieldRef: - description: 'Selects a resource of the container: only - resources limits and requests 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 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 type: string optional: - description: Specify whether the Secret or its key - must be defined type: boolean required: - key @@ -10288,13 +10820,6 @@ spec: - redis-port type: string servicesRiggerAdditionalPodSpecAttributes: - description: ADVANCED USAGE USE AT YOUR OWN RISK - specify pod attributes - that are required for the rigger deployment pod. Pod attributes - managed by the operator might override these settings (Containers, - serviceAccountName, podTolerations, ImagePullSecrets, nodeSelector, - PriorityClassName, PodSecurityContext). Also make sure the attributes are supported - by the K8s version running on the cluster - the operator does - not validate that. properties: activeDeadlineSeconds: format: int64 @@ -10890,6 +11415,16 @@ spec: failureThreshold: format: int32 type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + type: string + required: + - port + type: object httpGet: properties: host: @@ -10980,6 +11515,16 @@ spec: failureThreshold: format: int32 type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + type: string + required: + - port + type: object httpGet: properties: host: @@ -11038,6 +11583,17 @@ spec: type: object resources: properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: set limits: additionalProperties: anyOf: @@ -11128,6 +11684,16 @@ spec: failureThreshold: format: int32 type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + type: string + required: + - port + type: object httpGet: properties: host: @@ -11251,6 +11817,8 @@ spec: type: boolean hostPID: type: boolean + hostUsers: + type: boolean hostname: type: string imagePullSecrets: @@ -11471,6 +12039,16 @@ spec: failureThreshold: format: int32 type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + type: string + required: + - port + type: object httpGet: properties: host: @@ -11565,6 +12143,16 @@ spec: failureThreshold: format: int32 type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + type: string + required: + - port + type: object httpGet: properties: host: @@ -11623,6 +12211,17 @@ spec: type: object resources: properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: set limits: additionalProperties: anyOf: @@ -11713,6 +12312,16 @@ spec: failureThreshold: format: int32 type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + type: string + required: + - port + type: object httpGet: properties: host: @@ -11824,6 +12433,13 @@ spec: type: string type: object x-kubernetes-map-type: atomic + os: + properties: + name: + type: string + required: + - name + type: object overhead: additionalProperties: anyOf: @@ -11848,12 +12464,43 @@ spec: - conditionType type: object type: array + resourceClaims: + items: + properties: + name: + type: string + source: + properties: + resourceClaimName: + type: string + resourceClaimTemplateName: + type: string + type: object + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map restartPolicy: type: string runtimeClassName: type: string schedulerName: type: string + schedulingGates: + items: + properties: + name: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map securityContext: properties: fsGroup: @@ -11973,9 +12620,21 @@ spec: type: string type: object type: object + matchLabelKeys: + items: + type: string + type: array + x-kubernetes-list-type: atomic maxSkew: format: int32 type: integer + minDomains: + format: int32 + type: integer + nodeAffinityPolicy: + type: string + nodeTaintsPolicy: + type: string topologyKey: type: string whenUnsatisfiable: @@ -12205,12 +12864,25 @@ spec: type: string name: type: string + namespace: + type: string required: - kind - name type: object resources: properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: set limits: additionalProperties: anyOf: @@ -12883,6 +13555,16 @@ spec: failureThreshold: format: int32 type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + type: string + required: + - port + type: object httpGet: properties: host: @@ -12977,6 +13659,16 @@ spec: failureThreshold: format: int32 type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + type: string + required: + - port + type: object httpGet: properties: host: @@ -13035,6 +13727,17 @@ spec: type: object resources: properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: set limits: additionalProperties: anyOf: @@ -13125,6 +13828,16 @@ spec: failureThreshold: format: int32 type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + type: string + required: + - port + type: object httpGet: properties: host: @@ -13230,12 +13943,8 @@ spec: type: object type: array slaveHA: - description: Slave high availability mechanism configuration. properties: slaveHAGracePeriod: - description: Time in seconds between when a node fails, and when - slave high availability mechanism starts relocating shards. If - set to 0, will not affect cluster configuration. format: int32 type: integer required: @@ -13244,13 +13953,8 @@ spec: uiAnnotations: additionalProperties: type: string - description: Annotations for Redis Enterprise UI service. - This annotations will override the overlapping global annotations set under spec.services.servicesAnnotations - The specified annotations will not override annotations that already exist and didn't originate from the operator, - except for the 'redis.io/last-keys' annotation which is reserved. type: object uiServiceType: - description: Type of service used to expose Redis Enterprise UI (https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types) enum: - ClusterIP - NodePort @@ -13258,8 +13962,6 @@ spec: - ExternalName type: string redisOnFlashSpec: - description: Stores configurations specific to redis on flash. If provided, the cluster will be capable of - creating redis on flash databases. properties: enabled: type: boolean @@ -13281,28 +13983,18 @@ spec: - storageClassName type: object upgradeSpec: - description: Specification for upgrades of Redis Enterprise properties: autoUpgradeRedisEnterprise: - description: Whether to upgrade Redis Enterprise automatically when - operator is upgraded type: boolean required: - autoUpgradeRedisEnterprise type: object username: - description: Username for the admin user of Redis Enterprise type: string vaultCASecret: - description: K8s secret name containing Vault's CA cert - defaults to - "vault-ca-cert" type: string volumes: - description: additional volumes items: - description: 'Volume represents a named volume in a pod that may be - accessed by any container in the pod. - More info: https://kubernetes.io/docs/concepts/storage/volumes' properties: awsElasticBlockStore: properties: diff --git a/deployer/redb_crd.yaml b/deployer/redb_crd.yaml index f129f03..61907cf 100644 --- a/deployer/redb_crd.yaml +++ b/deployer/redb_crd.yaml @@ -559,22 +559,29 @@ spec: - major - latest type: string - activeActiveName: - description: 'The Redis Enterprise Active Active Peering custom resource name this Resource is associated - with, also, the corresponding active active database name. - In case this resource is created manually - at the active active database creation this field must be filled via the user, - otherwise, the operator will assign this field automatically. - Note: this feature is currently unsupported.' - type: string - globalConfigurations: - description: 'Flag that determines if this is the default global configurations - for active active database. - In case this resource is created manually - at the active active database creation this field must be filled via the user - otherwise the operator will create the global configurations REDB automatically with default values. - Note: this feature is currently unsupported.' - type: boolean + activeActive: + description: Connection/ association to the Active-Active database. + properties: + name: + description: 'The the corresponding Active-Active database name, + Redis Enterprise Active Active Database custom resource name, this + Resource is associated with. In case this resource is created + manually at the active active database creation this field must + be filled via the user, otherwise, the operator will assign this + field automatically. Note: this feature is currently unsupported.' + type: string + participatingClusterName: + description: 'The corresponding participating cluster name, Redis + Enterprise Remote Cluster custom resource name, in the Active-Active + database, In case this resource is created manually at the active + active database creation this field must be filled via the user, + otherwise, the operator will assign this field automatically. + Note: this feature is currently unsupported.' + type: string + required: + - name + - participatingClusterName + type: object memcachedSaslSecretName: description: 'Credentials used for binary authentication in memcached databases. The credentials should be saved as an opaque secret and the name of that secret should be configured using this field. @@ -704,15 +711,28 @@ spec: version: description: Database compatibility version type: string - activeActiveName: - description: 'The Redis Enterprise Active Active Peering custom resource this Resource is associated - with, also, the corresponding active active database name. - Note: this feature is currently unsupported.' - type: string - globalConfigurations: - description: 'Flag to determine if this is the default global configurations - for active active database. - Note: this feature is currently unsupported.' - type: boolean + activeActive: + description: Connection/ association to the Active-Active database. + properties: + name: + description: 'The the corresponding Active-Active database name, + Redis Enterprise Active Active Database custom resource name, this + Resource is associated with. In case this resource is created + manually at the active active database creation this field must + be filled via the user, otherwise, the operator will assign this + field automatically. Note: this feature is currently unsupported.' + type: string + participatingClusterName: + description: 'The corresponding participating cluster name, Redis + Enterprise Remote Cluster custom resource name, in the Active-Active + database, In case this resource is created manually at the active + active database creation this field must be filled via the user, + otherwise, the operator will assign this field automatically. + Note: this feature is currently unsupported.' + type: string + required: + - name + - participatingClusterName + type: object type: object type: object diff --git a/usage-meter/Dockerfile b/usage-meter/Dockerfile index 6aa782e..6b028a9 100644 --- a/usage-meter/Dockerfile +++ b/usage-meter/Dockerfile @@ -1,7 +1,14 @@ -FROM python:3.8-slim +FROM registry.access.redhat.com/ubi8-minimal -RUN apt-get update && apt-get install -y curl && apt-get install -y openssl -RUN pip install kubernetes requests +# Update base packages. +RUN microdnf update -y + +# Install python 3.8 and requirements. +RUN microdnf install python38 && \ + pip3 install kubernetes requests && \ + microdnf remove python38-setuptools python38-pip + +# Setup the usage-meter app. RUN mkdir -p /app COPY run.sh /app/ COPY common.py /app/ From e08adcbf68b90366b8b6235899ed9959befe1ede Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Sun, 30 Apr 2023 12:49:41 +0000 Subject: [PATCH 109/120] Release version 6.4.2-5 --- Makefile | 4 ++-- README.md | 4 ++-- chart/redis-operator/Chart.yaml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 8766d44..6053c7c 100644 --- a/Makefile +++ b/Makefile @@ -21,10 +21,10 @@ $(info ---- REGISTRY = $(REGISTRY)) CHART_NAME := redis-operator $(info ---- CHART_NAME = $(CHART_NAME)) -REDIS_TAG ?= 6.4.2-43 +REDIS_TAG ?= 6.4.2-61 $(info ---- REDIS_TAG = $(REDIS_TAG)) -OPERATOR_TAG ?= 6.4.2-4 +OPERATOR_TAG ?= 6.4.2-5 $(info ---- OPERATOR_TAG = $(OPERATOR_TAG)) # The repo to pull the operator image from Docker Hub registry. diff --git a/README.md b/README.md index 7a12905..2dace0a 100644 --- a/README.md +++ b/README.md @@ -88,8 +88,8 @@ Redis version tags are in the format Major.Minor.Patch-Sub but GKE Marketplace r ```shell export APP_INSTANCE_NAME=redis-enterprise-operator export NAMESPACE=redis -export TAG=6.4.2-4 -export DEPLOYER_TAG=6.424 +export TAG=6.4.2-5 +export DEPLOYER_TAG=6.425 export REPO=gcr.io/cloud-marketplace/redislabs-public/redis-enterprise ``` diff --git a/chart/redis-operator/Chart.yaml b/chart/redis-operator/Chart.yaml index ce0c1e8..72faba9 100644 --- a/chart/redis-operator/Chart.yaml +++ b/chart/redis-operator/Chart.yaml @@ -1,3 +1,3 @@ apiVersion: "v2" name: redis-operator -version: "1.25" +version: "1.26" From d2b0dbd5cc1c8463235feaa2327fe7fb89f4c18d Mon Sep 17 00:00:00 2001 From: Brad Ascar Date: Fri, 5 May 2023 08:42:15 -0400 Subject: [PATCH 110/120] Update README.md Added wording around the license for billing integration not being the same as the Redis license. --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 2dace0a..941cb78 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ gcloud auth configure-docker ``` ### Obtain license key from Google Cloud Marketplace +!! Not to be confused with the Redis license, which you would receive from Redis. This license key simply enables the pay as you go billing integration into your GKE account!! #### Purchase Redis Enterprise on GKE via GCP MP in a GCP project @@ -42,6 +43,8 @@ See the following screenshot for an example. Click the “Generate license key Save the license key file preferably as ```license-key.yaml```. +!! Reminder, not to be confused with the Redis license, which you would receive from Redis. This license key simply enables the pay as you go billing integration into your GKE account!! + ### Permissions Users who build and deploy the solution would need "Kubernetes Engine Admin" and "Editor" permissions. From 653fa9520f212d7936f1cac83a39b98481bf7848 Mon Sep 17 00:00:00 2001 From: alon-zada <84660065+alon-zada@users.noreply.github.com> Date: Mon, 28 Aug 2023 11:32:15 +0300 Subject: [PATCH 111/120] 7.2.4 2 (#39) * Release version 7.2.4-2 * 7.2.4-2. --------- Co-authored-by: Ubuntu --- Makefile | 4 +- README.md | 4 +- apptest/tester/Dockerfile | 2 +- .../templates/deployment/operator.yaml | 37 +- deployer/Dockerfile | 7 +- deployer/reaadb_crd.yaml | 732 ++++++++++++++++++ deployer/rec_crd.yaml | 200 ++++- deployer/redb_crd.yaml | 19 +- deployer/rerc_crd.yaml | 85 ++ schema.yaml | 6 + 10 files changed, 1067 insertions(+), 29 deletions(-) create mode 100644 deployer/reaadb_crd.yaml create mode 100644 deployer/rerc_crd.yaml diff --git a/Makefile b/Makefile index 6053c7c..eb542e5 100644 --- a/Makefile +++ b/Makefile @@ -21,10 +21,10 @@ $(info ---- REGISTRY = $(REGISTRY)) CHART_NAME := redis-operator $(info ---- CHART_NAME = $(CHART_NAME)) -REDIS_TAG ?= 6.4.2-61 +REDIS_TAG ?= 7.2.4-52 $(info ---- REDIS_TAG = $(REDIS_TAG)) -OPERATOR_TAG ?= 6.4.2-5 +OPERATOR_TAG ?= 7.2.4-2 $(info ---- OPERATOR_TAG = $(OPERATOR_TAG)) # The repo to pull the operator image from Docker Hub registry. diff --git a/README.md b/README.md index 941cb78..c2f2bd8 100644 --- a/README.md +++ b/README.md @@ -91,8 +91,8 @@ Redis version tags are in the format Major.Minor.Patch-Sub but GKE Marketplace r ```shell export APP_INSTANCE_NAME=redis-enterprise-operator export NAMESPACE=redis -export TAG=6.4.2-5 -export DEPLOYER_TAG=6.425 +export TAG=7.2.4-2 +export DEPLOYER_TAG=7.242 export REPO=gcr.io/cloud-marketplace/redislabs-public/redis-enterprise ``` diff --git a/apptest/tester/Dockerfile b/apptest/tester/Dockerfile index 9112bc2..e8488a1 100644 --- a/apptest/tester/Dockerfile +++ b/apptest/tester/Dockerfile @@ -1,4 +1,4 @@ -FROM gcr.io/cloud-marketplace-tools/testrunner:0.1.2 +FROM gcr.io/cloud-marketplace-tools/testrunner:0.1.5 ENV WAIT_FOR_READY_TIMEOUT 3600 ENV TESTER_TIMEOUT 3600 diff --git a/chart/redis-operator/templates/deployment/operator.yaml b/chart/redis-operator/templates/deployment/operator.yaml index 0630c3c..7df03c6 100644 --- a/chart/redis-operator/templates/deployment/operator.yaml +++ b/chart/redis-operator/templates/deployment/operator.yaml @@ -10,12 +10,17 @@ spec: selector: matchLabels: name: redis-enterprise-operator + strategy: + type: Recreate template: metadata: labels: name: redis-enterprise-operator app: redis-enterprise spec: + securityContext: + seccompProfile: + type: RuntimeDefault serviceAccountName: {{ .Values.operator.serviceAccountName }} initContainers: {{- include "initContainerWaitForCRDsDeploy" . | nindent 6 }} @@ -23,7 +28,8 @@ spec: - name: redis-enterprise-operator image: {{ .Values.operator.image.repository }}:{{ .Values.operator.image.tag }} command: - - redis-enterprise-operator + - operator-root + - operator imagePullPolicy: Always envFrom: - configMapRef: @@ -62,10 +68,19 @@ spec: path: /healthz port: 8080 scheme: HTTP + securityContext: + privileged: false + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + runAsNonRoot: true + capabilities: + drop: + - ALL - name: admission image: {{ .Values.operator.image.repository }}:{{ .Values.operator.image.tag }} command: - - /usr/local/bin/admission + - operator-root + - admission imagePullPolicy: Always envFrom: - configMapRef: @@ -108,6 +123,14 @@ spec: path: /liveness port: 8443 scheme: HTTPS + securityContext: + privileged: false + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + runAsNonRoot: true + capabilities: + drop: + - ALL - name: usage-meter image: {{ .Values.usagemeter.image }}:{{ .Values.usagemeter.tag }} imagePullPolicy: Always @@ -118,6 +141,11 @@ spec: value: "http://localhost:6080/report" - name: METRICS value: "--time-period 3600 --shards --report-value shards_used --metric-name redislabs_db_shard_hour" + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL - name: ubbagent image: gcr.io/cloud-marketplace-tools/metering/ubbagent imagePullPolicy: IfNotPresent @@ -140,6 +168,11 @@ spec: secretKeyRef: name: "{{ .Values.reportingSecret }}" key: consumer-id + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL volumeMounts: - name: ubb-configmap mountPath: /ubbagent/ diff --git a/deployer/Dockerfile b/deployer/Dockerfile index a581f80..3fb5051 100644 --- a/deployer/Dockerfile +++ b/deployer/Dockerfile @@ -1,11 +1,12 @@ ARG MARKETPLACE_TOOLS_TAG -FROM marketplace.gcr.io/google/debian9 AS build +FROM marketplace.gcr.io/google/debian11 AS build ARG CHART_NAME RUN apt-get update \ - && apt-get install -y --no-install-recommends gettext jq + && apt-get install -y --no-install-recommends gettext jq \ + && apt remove golang ADD chart/$CHART_NAME /tmp/chart RUN cd /tmp && tar -czvf /tmp/$CHART_NAME.tar.gz chart @@ -45,4 +46,6 @@ COPY --from=build /tmp/schema.yaml /data/ RUN mkdir -p /crd_to_create ADD deployer/rec_crd.yaml /crd_to_create/rec_crd.yaml ADD deployer/redb_crd.yaml /crd_to_create/redb_crd.yaml +ADD deployer/rerc_crd.yaml /crd_to_create/rerc_crd.yaml +ADD deployer/reaadb_crd.yaml /crd_to_create/reaadb_crd.yaml diff --git a/deployer/reaadb_crd.yaml b/deployer/reaadb_crd.yaml new file mode 100644 index 0000000..5382412 --- /dev/null +++ b/deployer/reaadb_crd.yaml @@ -0,0 +1,732 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: redisenterpriseactiveactivedatabases.app.redislabs.com + labels: + app: redis-enterprise +spec: + group: app.redislabs.com + names: + kind: RedisEnterpriseActiveActiveDatabase + listKind: RedisEnterpriseActiveActiveDatabaseList + plural: redisenterpriseactiveactivedatabases + singular: redisenterpriseactiveactivedatabase + shortNames: + - reaadb + preserveUnknownFields: false + scope: Namespaced + versions: + - name: v1alpha1 + served: true + storage: true + additionalPrinterColumns: + - jsonPath: .status.status + name: Status + type: string + - jsonPath: .status.specStatus + name: Spec Status + type: string + - jsonPath: .status.linkedRedbs[*] + name: Linked REDBs + type: string + subresources: + status: {} + schema: + openAPIV3Schema: + description: RedisEnterpriseActiveActiveDatabase is the Schema for the redisenterpriseactiveactivedatabase + API + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + status: + description: RedisEnterpriseActiveActiveDatabaseStatus defines the observed + state of RedisEnterpriseActiveActiveDatabase + properties: + status: + description: The status of the active active database. + type: string + specStatus: + description: Whether the desired specification is valid + type: string + linkedRedbs: + description: The linked REDBs. + items: + type: string + type: array + participatingClusters: + description: The list of instances/ clusters statuses. + items: + description: Status of participating cluster. + properties: + name: + description: The name of the remote cluster CR that is linked. + type: string + id: + description: The corresponding ID of the instance in the active-active database. + format: int64 + type: integer + required: + - name + type: object + type: array + guid: + description: The active-active database corresponding GUID. + type: string + lastTaskUid: + description: The last active-active database task UID. + type: string + redisEnterpriseCluster: + description: The Redis Enterprise Cluster Object this Resource is associated + with + type: string + secretsStatus: + description: The status of the secrets + items: + description: Status of secrets. + properties: + name: + description: The name of the secret. + type: string + status: + description: The status of the secret. + enum: + - Valid + - Invalid + type: string + required: + - name + type: object + type: array + type: object + spec: + description: RedisEnterpriseActiveActiveDatabaseSpec defines the desired + state of RedisEnterpriseActiveActiveDatabase + properties: + redisEnterpriseCluster: + description: Connection to Redis Enterprise Cluster + properties: + name: + description: The name of the Redis Enterprise Cluster where the + database should be stored. + type: string + required: + - name + type: object + participatingClusters: + description: The list of instances/ clusters specifications and configurations. + items: + properties: + name: + description: The name of the remote cluster CR to link. + type: string + required: + - name + type: object + type: array + globalConfigurations: + description: The Active-Active database global configurations, + contains the global properties for each of the participating clusters/ instances databases within the Active-Active database. + properties: + activeActive: + description: Connection/ association to the Active-Active database. + properties: + name: + description: 'The the corresponding Active-Active database name, + Redis Enterprise Active Active Database custom resource name, + this Resource is associated with. In case this resource is + created manually at the active active database creation this + field must be filled via the user, otherwise, the operator + will assign this field automatically. Note: this feature is + currently unsupported.' + type: string + participatingClusterName: + description: 'The corresponding participating cluster name, + Redis Enterprise Remote Cluster custom resource name, in the + Active-Active database, In case this resource is created manually + at the active active database creation this field must be + filled via the user, otherwise, the operator will assign this + field automatically. Note: this feature is currently unsupported.' + type: string + required: + - name + - participatingClusterName + type: object + alertSettings: + description: Settings for database alerts + properties: + bdb_backup_delayed: + description: Periodic backup has been delayed for longer than + specified threshold value [minutes] + properties: + enabled: + description: Alert enabled or disabled + type: boolean + threshold: + description: Threshold for alert going on/off + type: string + required: + - enabled + - threshold + type: object + bdb_crdt_src_high_syncer_lag: + description: Active-active source - sync lag is higher than + specified threshold value [seconds] + properties: + enabled: + description: Alert enabled or disabled + type: boolean + threshold: + description: Threshold for alert going on/off + type: string + required: + - enabled + - threshold + type: object + bdb_crdt_src_syncer_connection_error: + description: Active-active source - sync has connection error + while trying to connect replica source + properties: + enabled: + description: Alert enabled or disabled + type: boolean + threshold: + description: Threshold for alert going on/off + type: string + required: + - enabled + - threshold + type: object + bdb_crdt_src_syncer_general_error: + description: Active-active source - sync encountered in general + error + properties: + enabled: + description: Alert enabled or disabled + type: boolean + threshold: + description: Threshold for alert going on/off + type: string + required: + - enabled + - threshold + type: object + bdb_high_latency: + description: Latency is higher than specified threshold value + [micro-sec] + properties: + enabled: + description: Alert enabled or disabled + type: boolean + threshold: + description: Threshold for alert going on/off + type: string + required: + - enabled + - threshold + type: object + bdb_high_throughput: + description: Throughput is higher than specified threshold + value [requests / sec.] + properties: + enabled: + description: Alert enabled or disabled + type: boolean + threshold: + description: Threshold for alert going on/off + type: string + required: + - enabled + - threshold + type: object + bdb_long_running_action: + description: An alert for state-machines that are running + for too long + properties: + enabled: + description: Alert enabled or disabled + type: boolean + threshold: + description: Threshold for alert going on/off + type: string + required: + - enabled + - threshold + type: object + bdb_low_throughput: + description: Throughput is lower than specified threshold + value [requests / sec.] + properties: + enabled: + description: Alert enabled or disabled + type: boolean + threshold: + description: Threshold for alert going on/off + type: string + required: + - enabled + - threshold + type: object + bdb_ram_dataset_overhead: + description: Dataset RAM overhead of a shard has reached the + threshold value [% of its RAM limit] + properties: + enabled: + description: Alert enabled or disabled + type: boolean + threshold: + description: Threshold for alert going on/off + type: string + required: + - enabled + - threshold + type: object + bdb_ram_values: + description: Percent of values kept in a shard's RAM is lower + than [% of its key count] + properties: + enabled: + description: Alert enabled or disabled + type: boolean + threshold: + description: Threshold for alert going on/off + type: string + required: + - enabled + - threshold + type: object + bdb_replica_src_high_syncer_lag: + description: Replica-of source - sync lag is higher than specified + threshold value [seconds] + properties: + enabled: + description: Alert enabled or disabled + type: boolean + threshold: + description: Threshold for alert going on/off + type: string + required: + - enabled + - threshold + type: object + bdb_replica_src_syncer_connection_error: + description: Replica-of source - sync has connection error + while trying to connect replica source + properties: + enabled: + description: Alert enabled or disabled + type: boolean + threshold: + description: Threshold for alert going on/off + type: string + required: + - enabled + - threshold + type: object + bdb_shard_num_ram_values: + description: Number of values kept in a shard's RAM is lower + than [values] + properties: + enabled: + description: Alert enabled or disabled + type: boolean + threshold: + description: Threshold for alert going on/off + type: string + required: + - enabled + - threshold + type: object + bdb_size: + description: Dataset size has reached the threshold value + [% of the memory limit] + properties: + enabled: + description: Alert enabled or disabled + type: boolean + threshold: + description: Threshold for alert going on/off + type: string + required: + - enabled + - threshold + type: object + type: object + backup: + description: Target for automatic database backups. + properties: + abs: + properties: + absSecretName: + description: The name of the secret that holds ABS credentials. + The secret must contain the keys "AccountName" and "AccountKey", + and these must hold the corresponding credentials + type: string + container: + description: Azure Blob Storage container name. + type: string + subdir: + description: Optional. Azure Blob Storage subdir under + container. + type: string + required: + - absSecretName + - container + type: object + ftp: + properties: + url: + description: a URI of the "ftps://[USER[:PASSWORD]@]HOST[:PORT]/PATH[/]" + format + type: string + required: + - url + type: object + gcs: + description: GoogleStorage + properties: + bucketName: + description: Google Storage bucket name. + type: string + gcsSecretName: + description: The name of the secret that holds the Google + Cloud Storage credentials. The secret must contain the + keys "CLIENT_ID", "PRIVATE_KEY", "PRIVATE_KEY_ID", "CLIENT_EMAIL" + and these must hold the corresponding credentials. The + keys should correspond to the values in the key JSON. + type: string + subdir: + description: Optional. Google Storage subdir under bucket. + type: string + required: + - bucketName + - gcsSecretName + type: object + interval: + description: Backup Interval in seconds + type: integer + mount: + description: MountPointStorage + properties: + path: + description: Path to the local mount point. You must create + the mount point on all nodes, and the redislabs:redislabs + user must have read and write permissions on the local + mount point. + type: string + required: + - path + type: object + s3: + properties: + awsSecretName: + description: The name of the secret that holds the AWS + credentials. The secret must contain the keys "AWS_ACCESS_KEY_ID" + and "AWS_SECRET_ACCESS_KEY", and these must hold the + corresponding credentials. + type: string + bucketName: + description: Amazon S3 bucket name. + type: string + subdir: + description: Optional. Amazon S3 subdir under bucket. + type: string + required: + - awsSecretName + - bucketName + type: object + sftp: + properties: + sftp_url: + description: SFTP url + type: string + sftpSecretName: + description: The name of the secret that holds SFTP credentials. + The secret must contain the "Key" key, which is the + SSH private key for connecting to the sftp server. + type: string + required: + - sftpSecretName + - sftp_url + type: object + swift: + properties: + auth_url: + description: Swift service authentication URL. + type: string + container: + description: Swift object store container for storing + the backup files. + type: string + prefix: + description: Optional. Prefix (path) of backup files in + the swift container. + type: string + swiftSecretName: + description: 'The name of the secret that holds Swift + credentials. The secret must contain the keys "Key" + and "User", and these must hold the corresponding credentials: + service access key and service user name (pattern for + the latter does not allow special characters &,<,>,")' + type: string + required: + - auth_url + - container + - swiftSecretName + type: object + type: object + clientAuthenticationCertificates: + description: The Secrets containing TLS Client Certificate to + use for Authentication + items: + type: string + type: array + dataInternodeEncryption: + description: Internode encryption (INE) setting. An optional boolean + setting, overriding a similar cluster-wide policy. If set to + False, INE is guaranteed to be turned off for this DB (regardless + of cluster-wide policy). If set to True, INE will be turned + on, unless the capability is not supported by the DB ( in such + a case we will get an error and database creation will fail). + If left unspecified, will be disabled if internode encryption + is not supported by the DB (regardless of cluster default). + Deleting this property after explicitly setting its value shall + have no effect. + type: boolean + databasePort: + description: Database port number. TCP port on which the database + is available. Will be generated automatically if omitted. can + not be changed after creation + type: integer + databaseSecretName: + description: The name of the secret that holds the password + to the database (redis databases only). + If secret does not exist, it will be created. + To define the password, create an opaque secret and set the name in the spec. + The password will be taken from the value of the 'password' key. + Use an empty string as value within the secret to disable authentication for the database. + Notes - For Active-Active databases this secret will not be automatically created, + and also, memcached databases must not be set with a value, + and a secret/password will not be automatically created for them. + Use the memcachedSaslSecretName field to set authentication parameters for memcached databases. + type: string + defaultUser: + description: Is connecting with a default user allowed? If disabled, + the DatabaseSecret will not be created or updated + type: boolean + evictionPolicy: + description: Database eviction policy. see more https://docs.redislabs.com/latest/rs/administering/database-operations/eviction-policy/ + type: string + isRof: + description: Whether it is an RoF database or not. Applicable + only for databases of type "REDIS". Assumed to be false if left + blank. + type: boolean + memcachedSaslSecretName: + description: Credentials used for binary authentication in memcached + databases. The credentials should be saved as an opaque secret + and the name of that secret should be configured using this + field. For username, use 'username' as the key and the actual + username as the value. For password, use 'password' as the key + and the actual password as the value. Note that connections + are not encrypted. + type: string + memorySize: + description: memory size of database. use formats like 100MB, + 0.1GB. minimum value in 100MB. When redis on flash (RoF) is + enabled, this value refers to RAM+Flash memory, and it must + not be below 1GB. + type: string + modulesList: + description: List of modules associated with database. + Note - For Active-Active databases this feature is currently in preview. For + this feature to take effect for Active-Active databases, set a boolean environment variable + with the name "ENABLE_ALPHA_FEATURES" to True. This variable can + be set via the redis-enterprise-operator pod spec, or through the + operator-environment-config Config Map. + items: + description: 'Redis Enterprise Module: https://redislabs.com/redis-enterprise/modules/' + properties: + config: + description: Module command line arguments e.g. VKEY_MAX_ENTITY_COUNT + 30 + type: string + name: + description: The module's name e.g "ft" for redissearch + type: string + uid: + description: Module's uid - do not set, for system use only + nolint:staticcheck // custom json tag unknown to the linter + type: string + version: + description: Module's semantic version e.g "1.6.12" + type: string + required: + - name + - version + type: object + type: array + ossCluster: + description: OSS Cluster mode option. Note that not all client + libraries support OSS cluster mode. + type: boolean + persistence: + description: Database on-disk persistence policy + enum: + - disabled + - aofEverySecond + - aofAlways + - snapshotEvery1Hour + - snapshotEvery6Hour + - snapshotEvery12Hour + type: string + proxyPolicy: + description: 'The policy used for proxy binding to the endpoint. + Supported proxy policies are: single/all-master-shards/all-nodes + When left blank, the default value will be chosen according + to the value of ossCluster - single if disabled, all-master-shards + when enabled' + type: string + rackAware: + description: 'Whether database should be rack aware. This improves + availability - more information: https://docs.redislabs.com/latest/rs/concepts/high-availability/rack-zone-awareness/' + type: boolean + redisEnterpriseCluster: + description: Connection to Redis Enterprise Cluster + properties: + name: + description: The name of the Redis Enterprise Cluster where + the database should be stored. + type: string + required: + - name + type: object + redisVersion: + description: Redis OSS version. For existing databases - Upgrade + Redis OSS version. For new databases - the version which the + database will be created with. If set to 'major' - will always + upgrade to the most recent major Redis version. If set to 'latest' + - will always upgrade to the most recent Redis version. Depends + on 'redisUpgradePolicy' - if you want to set the value to 'latest' + for some databases, you must set redisUpgradePolicy on the cluster + before. Possible values are 'major' or 'latest' When using upgrade + - make sure to backup the database before. This value is used + only for database type 'redis' + enum: + - major + - latest + type: string + replicaSources: + description: What databases to replicate from + items: + properties: + clientKeySecret: + description: 'Secret that defines the client certificate + and key used by the syncer in the target database cluster. + The secret must have 2 keys in its map: "cert" which is + the PEM encoded certificate, and "key" which is the PEM + encoded private key.' + type: string + compression: + description: GZIP compression level (0-6) to use for replication. + type: integer + replicaSourceName: + description: The name of the resource from which the source + database URI is derived. The type of resource must match + the type specified in the ReplicaSourceType field. + type: string + replicaSourceType: + description: The type of resource from which the source + database URI is derived. If set to 'SECRET', the source + database URI is derived from the secret named in the ReplicaSourceName + field. The secret must have a key named 'uri' that defines + the URI of the source database in the form of 'redis://...'. + The type of secret (kubernetes, vault, ...) is determined + by the secret mechanism used by the underlying REC object. + If set to 'REDB', the source database URI is derived from + the RedisEnterpriseDatabase resource named in the ReplicaSourceName + field. + type: string + serverCertSecret: + description: 'Secret that defines the server certificate + used by the proxy in the source database cluster. The + secret must have 1 key in its map: "cert" which is the + PEM encoded certificate.' + type: string + tlsSniName: + description: TLS SNI name to use for the replication link. + type: string + required: + - replicaSourceName + - replicaSourceType + type: object + type: array + replication: + description: In-memory database replication. When enabled, database + will have replica shard for every master - leading to higher + availability. + type: boolean + rofRamSize: + description: The size of the RAM portion of an RoF database. Similarly + to "memorySize" use formats like 100MB, 0.1GB It must be at + least 10% of combined memory size (RAM+Flash), as specified + by "memorySize". + type: string + rolesPermissions: + description: List of Redis Enteprise ACL and Role bindings to + apply + items: + description: Redis Enterprise Role and ACL Binding + properties: + acl: + description: 'Acl Name of RolePermissionType (note: use + exact name of the ACL from the Redis Enterprise ACL list, + case sensitive)' + type: string + role: + description: 'Role Name of RolePermissionType (note: use + exact name of the role from the Redis Enterprise role + list, case sensitive)' + type: string + type: + description: Type of Redis Enterprise Database Role Permission + type: string + required: + - acl + - role + - type + type: object + type: array + shardCount: + description: Number of database server-side shards + type: integer + shardsPlacement: + description: Control the density of shards - should they reside + on as few or as many nodes as possible. Available options are + "dense" or "sparse". If left unset, defaults to "dense". + type: string + tlsMode: + description: Require SSL authenticated and encrypted connections + to the database. enabled - all incoming connections to the Database + must use SSL. disabled - no incoming connection to the Database + should use SSL. replica_ssl - databases that replicate from + this one need to use SSL. + enum: + - disabled + - enabled + - replica_ssl + type: string + type: + description: The type of the database. + enum: + - redis + - memcached + type: string + type: object + required: + - participatingClusters + type: object + type: object diff --git a/deployer/rec_crd.yaml b/deployer/rec_crd.yaml index 76f3c89..4e87258 100644 --- a/deployer/rec_crd.yaml +++ b/deployer/rec_crd.yaml @@ -124,6 +124,8 @@ spec: properties: dbType: type: string + major: + type: boolean version: type: string required: @@ -145,6 +147,9 @@ spec: field in the RedisEnterpriseCluster resource. type: boolean type: object + ingressOrRouteMethodStatus: + description: The ingressOrRouteSpec/ActiveActive spec method that exist + type: string spec: description: RedisEnterpriseClusterSpec defines the desired state of RedisEnterpriseCluster properties: @@ -268,12 +273,16 @@ spec: containerTimezone: description: Container timezone configuration. While the default timezone on all containers is UTC, this setting can be used to set the timezone - on services rigger/bootstrapper/RS containers. Currently the only - supported value is to propagate the host timezone to all containers. + on services rigger/bootstrapper/RS containers. You can either propagate + the hosts timezone to RS pods or set it manually via timezoneName. properties: propagateHost: - description: Identifies that container timezone should be in sync with the host. + description: Identifies that container timezone should be in sync with the host, this + option mounts a hostPath volume onto RS pods that could be restricted in some systems. type: object + timezoneName: + description: POSIX-style timezone name as a string to be passed as EnvVar to RE pods, e.g. "Europe/London". + type: string type: object createServiceAccount: description: Whether to create service account @@ -320,12 +329,75 @@ spec: enforceIPv4: description: Sets ENFORCE_IPV4 environment variable type: boolean + extraEnvVars: + description: 'ADVANCED USAGE: use carefully. Add environment variables + to RS StatefulSet''s containers.' + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + resourceFieldRef: + properties: + containerName: + type: string + divisor: + 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 + resource: + type: string + required: + - resource + type: object + secretKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array extraLabels: additionalProperties: type: string description: Labels that the user defines for their convenience type: object hostAliases: + description: Adds hostAliases entries to the Redis Enterprise pods items: description: HostAlias holds the mapping between IP and hostnames that will be injected as an entry in the pod's hosts file. @@ -342,11 +414,7 @@ spec: type: array ingressOrRouteSpec: description: Access configurations for the Redis Enterprise Cluster - and Databases. Note - this feature is currently in preview. For - this feature to take effect, set a boolean environment variable - with the name "ENABLE_ALPHA_FEATURES" to True. This variable can - be set via the redis-enterprise-operator pod spec, or through the - operator-environment-config Config Map. At most one of ingressOrRouteSpec + and Databases. At most one of ingressOrRouteSpec or activeActive fields can be set at the same time. properties: apiFqdnUrl: @@ -502,16 +570,13 @@ spec: the protocol is LDAPS or STARTTLS. type: string cacheTTLSeconds: - default: 300 description: The maximum TTL of cached entries. type: integer enabledForControlPlane: - default: false description: Whether to enable LDAP for control plane access. Disabled by default. type: boolean enabledForDataPlane: - default: false description: Whether to enable LDAP for data plane access. Disabled by default. type: boolean @@ -620,7 +685,12 @@ spec: podAnnotations: additionalProperties: type: string - description: pod annotations + description: annotations for the service rigger and redis enterprise pods + type: object + redisEnterprisePodAnnotations: + additionalProperties: + type: string + description: annotations for redis enterprise pod type: object podAntiAffinity: description: 'Override for the default anti-affinity rules of the Redis @@ -3685,6 +3755,9 @@ spec: type: object type: array serviceNaming: + description: Used to determine how to name the services created automatically when a database is created. + When bdb_name is used, the database name will be also used for the service name. + When redis-port is used, the service will be named redis-. enum: - bdb_name - redis-port @@ -6220,6 +6293,11 @@ spec: type: object type: array type: object + podAnnotations: + additionalProperties: + type: string + description: annotations for the service rigger pod + type: object type: object sideContainersSpec: items: @@ -6865,9 +6943,13 @@ 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 + bigStoreDriver: + type: string + enum: + - rocksdb + - speedb required: - enabled - - flashStorageEngine - storageClassName type: object upgradeSpec: @@ -7578,11 +7660,15 @@ spec: type: string version: type: string + major: + type: boolean required: - dbType - version type: object type: array + ingressOrRouteMethodStatus: + type: string managedAPIs: properties: ldap: @@ -7671,6 +7757,8 @@ spec: properties: propagateHost: type: object + timezoneName: + type: string type: object createServiceAccount: type: boolean @@ -7695,11 +7783,74 @@ spec: type: object enforceIPv4: type: boolean + extraEnvVars: + description: 'ADVANCED USAGE: use carefully. Add environment variables + to RS StatefulSet''s containers.' + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + resourceFieldRef: + properties: + containerName: + type: string + divisor: + 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 + resource: + type: string + required: + - resource + type: object + secretKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array extraLabels: additionalProperties: type: string type: object hostAliases: + description: Adds hostAliases entries to the Redis Enterprise pods items: properties: hostnames: @@ -7798,13 +7949,10 @@ spec: caCertificateSecretName: type: string cacheTTLSeconds: - default: 300 type: integer enabledForControlPlane: - default: false type: boolean enabledForDataPlane: - default: false type: boolean protocol: enum: @@ -7870,6 +8018,12 @@ spec: podAnnotations: additionalProperties: type: string + description: annotations for the service rigger and redis enterprise pods + type: object + redisEnterprisePodAnnotations: + additionalProperties: + type: string + description: annotations for redis enterprise pod type: object podAntiAffinity: properties: @@ -10815,6 +10969,9 @@ spec: type: object type: array serviceNaming: + description: Used to determine how to name the services created automatically when a database is created. + When bdb_name is used, the database name will be also used for the service name. + When redis-port is used, the service will be named redis-. enum: - bdb_name - redis-port @@ -13343,6 +13500,11 @@ spec: type: object type: array type: object + podAnnotations: + additionalProperties: + type: string + description: annotations for the service rigger pod + type: object type: object sideContainersSpec: items: @@ -13977,9 +14139,13 @@ 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 + bigStoreDriver: + type: string + enum: + - rocksdb + - speedb required: - enabled - - flashStorageEngine - storageClassName type: object upgradeSpec: diff --git a/deployer/redb_crd.yaml b/deployer/redb_crd.yaml index 61907cf..69be6fb 100644 --- a/deployer/redb_crd.yaml +++ b/deployer/redb_crd.yaml @@ -372,8 +372,16 @@ spec: changed after creation type: integer databaseSecretName: - description: The name of the K8s secret that holds the password to the - database. + description: The name of the secret that holds the password + to the database (redis databases only). + If secret does not exist, it will be created. + To define the password, create an opaque secret and set the name in the spec. + The password will be taken from the value of the 'password' key. + Use an empty string as value within the secret to disable authentication for the database. + Notes - For Active-Active databases this secret will not be automatically created, + and also, memcached databases must not be set with a value, + and a secret/password will not be automatically created for them. + Use the memcachedSaslSecretName field to set authentication parameters for memcached databases. type: string defaultUser: description: Is connecting with a default user allowed? @@ -391,7 +399,12 @@ spec: and it must not be below 1GB. type: string modulesList: - description: List of modules associated with database + description: List of modules associated with database. + Note - For Active-Active databases this feature is currently in preview. For + this feature to take effect for Active-Active databases, set a boolean environment variable + with the name "ENABLE_ALPHA_FEATURES" to True. This variable can + be set via the redis-enterprise-operator pod spec, or through the + operator-environment-config Config Map. items: description: 'Redis Enterprise Module: https://redislabs.com/redis-enterprise/modules/' properties: diff --git a/deployer/rerc_crd.yaml b/deployer/rerc_crd.yaml new file mode 100644 index 0000000..344cef6 --- /dev/null +++ b/deployer/rerc_crd.yaml @@ -0,0 +1,85 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: redisenterpriseremoteclusters.app.redislabs.com + labels: + app: redis-enterprise +spec: + group: app.redislabs.com + names: + kind: RedisEnterpriseRemoteCluster + listKind: RedisEnterpriseRemoteClusterList + plural: redisenterpriseremoteclusters + singular: redisenterpriseremotecluster + shortNames: + - rerc + scope: Namespaced + versions: + - name: v1alpha1 + served: true + storage: true + additionalPrinterColumns: + - jsonPath: .status.status + name: Status + type: string + - jsonPath: .status.specStatus + name: Spec Status + type: string + - jsonPath: .status.local + name: Local + type: string + subresources: + status: {} + schema: + openAPIV3Schema: + description: RedisEntepriseRemoteCluster represents a remote participating + cluster. + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + status: + properties: + local: + description: Indicates whether this object represents a local or a + remote cluster. + type: boolean + specStatus: + description: Whether the desired specification is valid. + type: string + status: + description: The status of the remote cluster. + type: string + observedGeneration: + description: The most recent generation observed for this RERC. It corresponds to the RERC's generation, which is updated by the API Server. + type: integer + type: object + spec: + properties: + apiFqdnUrl: + description: The URL of the cluster, will be used for the active-active + database URL. + type: string + dbFqdnSuffix: + description: The database URL suffix, will be used for the active-active + database replication endpoint and replication endpoint SNI. + type: string + recNamespace: + description: The namespace of the REC that the RERC is pointing at + type: string + recName: + description: The name of the REC that the RERC is pointing at + type: string + secretName: + description: 'The name of the secret containing cluster credentials. + Must be of the following format: "redis-enterprise-"' + type: string + required: + - apiFqdnUrl + - recName + - recNamespace + type: object + type: object diff --git a/schema.yaml b/schema.yaml index 8ec2d6c..ef71e51 100644 --- a/schema.yaml +++ b/schema.yaml @@ -77,6 +77,12 @@ properties: - "redisenterprisedatabases" - "redisenterprisedatabases/status" - "redisenterprisedatabases/finalizers" + - "redisenterpriseactiveactivedatabases" + - "redisenterpriseactiveactivedatabases/status" + - "redisenterpriseactiveactivedatabases/finalizers" + - "redisenterpriseremoteclusters" + - "redisenterpriseremoteclusters/status" + - "redisenterpriseremoteclusters/finalizers" verbs: ["delete", "deletecollection", "get", "list", "patch", "create", "update", "watch"] - apiGroups: [""] From fea491d0887804cb9abb1b104e844ae8f1a76715 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Thu, 21 Dec 2023 12:02:59 +0000 Subject: [PATCH 112/120] Release version 7.2.4-12 --- Makefile | 4 ++-- README.md | 4 ++-- chart/redis-operator/Chart.yaml | 2 +- .../redis-operator/templates/configmap/cr.yaml | 2 -- .../templates/deployment/operator.yaml | 4 ++++ deployer/rec_crd.yaml | 17 +++++++++++++++++ deployer/redb_crd.yaml | 2 ++ 7 files changed, 28 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index eb542e5..8766d44 100644 --- a/Makefile +++ b/Makefile @@ -21,10 +21,10 @@ $(info ---- REGISTRY = $(REGISTRY)) CHART_NAME := redis-operator $(info ---- CHART_NAME = $(CHART_NAME)) -REDIS_TAG ?= 7.2.4-52 +REDIS_TAG ?= 6.4.2-43 $(info ---- REDIS_TAG = $(REDIS_TAG)) -OPERATOR_TAG ?= 7.2.4-2 +OPERATOR_TAG ?= 6.4.2-4 $(info ---- OPERATOR_TAG = $(OPERATOR_TAG)) # The repo to pull the operator image from Docker Hub registry. diff --git a/README.md b/README.md index c2f2bd8..bfc1b23 100644 --- a/README.md +++ b/README.md @@ -91,8 +91,8 @@ Redis version tags are in the format Major.Minor.Patch-Sub but GKE Marketplace r ```shell export APP_INSTANCE_NAME=redis-enterprise-operator export NAMESPACE=redis -export TAG=7.2.4-2 -export DEPLOYER_TAG=7.242 +export TAG=6.4.2-4 +export DEPLOYER_TAG=6.424 export REPO=gcr.io/cloud-marketplace/redislabs-public/redis-enterprise ``` diff --git a/chart/redis-operator/Chart.yaml b/chart/redis-operator/Chart.yaml index 72faba9..ce0c1e8 100644 --- a/chart/redis-operator/Chart.yaml +++ b/chart/redis-operator/Chart.yaml @@ -1,3 +1,3 @@ apiVersion: "v2" name: redis-operator -version: "1.26" +version: "1.25" diff --git a/chart/redis-operator/templates/configmap/cr.yaml b/chart/redis-operator/templates/configmap/cr.yaml index 50e7b87..b5d0806 100644 --- a/chart/redis-operator/templates/configmap/cr.yaml +++ b/chart/redis-operator/templates/configmap/cr.yaml @@ -32,5 +32,3 @@ data: requests: cpu: "{{ .Values.operator.nodeCpu }}m" memory: "{{ .Values.operator.nodeMem }}Gi" - - diff --git a/chart/redis-operator/templates/deployment/operator.yaml b/chart/redis-operator/templates/deployment/operator.yaml index 7df03c6..ffa5bc1 100644 --- a/chart/redis-operator/templates/deployment/operator.yaml +++ b/chart/redis-operator/templates/deployment/operator.yaml @@ -97,6 +97,10 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name resources: limits: cpu: 1000m diff --git a/deployer/rec_crd.yaml b/deployer/rec_crd.yaml index 4e87258..4390f80 100644 --- a/deployer/rec_crd.yaml +++ b/deployer/rec_crd.yaml @@ -150,6 +150,8 @@ spec: ingressOrRouteMethodStatus: description: The ingressOrRouteSpec/ActiveActive spec method that exist type: string + redisEnterpriseIPFamily: + type: string spec: description: RedisEnterpriseClusterSpec defines the desired state of RedisEnterpriseCluster properties: @@ -270,6 +272,14 @@ spec: true. Note that this field is cleared automatically after the cluster is recovered type: boolean + redisEnterpriseIPFamily: + description: Reserved, future use, only for use if instructed by Redis. + IPFamily dictates what IP family to choose for pods' internal + and external communication. + type: string + enum: + - IPv4 + - IPv6 containerTimezone: description: Container timezone configuration. While the default timezone on all containers is UTC, this setting can be used to set the timezone @@ -7674,6 +7684,8 @@ spec: ldap: type: boolean type: object + redisEnterpriseIPFamily: + type: string spec: properties: activeActive: @@ -7753,6 +7765,11 @@ spec: type: string clusterRecovery: type: boolean + redisEnterpriseIPFamily: + type: string + enum: + - IPv4 + - IPv6 containerTimezone: properties: propagateHost: diff --git a/deployer/redb_crd.yaml b/deployer/redb_crd.yaml index 69be6fb..2449b5a 100644 --- a/deployer/redb_crd.yaml +++ b/deployer/redb_crd.yaml @@ -2,6 +2,8 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: redisenterprisedatabases.app.redislabs.com + labels: + app: redis-enterprise spec: group: app.redislabs.com names: From 1aa010835e0957318b41f8b98e183da8b4fbbb4c Mon Sep 17 00:00:00 2001 From: randv1r Date: Thu, 21 Dec 2023 14:29:16 +0200 Subject: [PATCH 113/120] update tags --- Makefile | 4 ++-- README.md | 4 ++-- chart/redis-operator/Chart.yaml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 8766d44..5a2a869 100644 --- a/Makefile +++ b/Makefile @@ -21,10 +21,10 @@ $(info ---- REGISTRY = $(REGISTRY)) CHART_NAME := redis-operator $(info ---- CHART_NAME = $(CHART_NAME)) -REDIS_TAG ?= 6.4.2-43 +REDIS_TAG ?= 7.2.4-92 $(info ---- REDIS_TAG = $(REDIS_TAG)) -OPERATOR_TAG ?= 6.4.2-4 +OPERATOR_TAG ?= 7.2.4-12 $(info ---- OPERATOR_TAG = $(OPERATOR_TAG)) # The repo to pull the operator image from Docker Hub registry. diff --git a/README.md b/README.md index bfc1b23..89c0c07 100644 --- a/README.md +++ b/README.md @@ -91,8 +91,8 @@ Redis version tags are in the format Major.Minor.Patch-Sub but GKE Marketplace r ```shell export APP_INSTANCE_NAME=redis-enterprise-operator export NAMESPACE=redis -export TAG=6.4.2-4 -export DEPLOYER_TAG=6.424 +export TAG=7.2.4-12 +export DEPLOYER_TAG=7.2412 export REPO=gcr.io/cloud-marketplace/redislabs-public/redis-enterprise ``` diff --git a/chart/redis-operator/Chart.yaml b/chart/redis-operator/Chart.yaml index ce0c1e8..6cff5a8 100644 --- a/chart/redis-operator/Chart.yaml +++ b/chart/redis-operator/Chart.yaml @@ -1,3 +1,3 @@ apiVersion: "v2" name: redis-operator -version: "1.25" +version: "1.27" From fa3fb1747d481cecb02877076d89eafbb7fa956b Mon Sep 17 00:00:00 2001 From: alon-zada <84660065+alon-zada@users.noreply.github.com> Date: Sun, 18 Feb 2024 13:31:10 +0200 Subject: [PATCH 114/120] Release version 7.4.2-2 (#41) * Release version 7.4.2-2 * fix. --------- Co-authored-by: Ubuntu --- Makefile | 4 ++-- README.md | 4 ++-- chart/redis-operator/Chart.yaml | 2 +- deployer/rec_crd.yaml | 8 ++++++++ deployer/redb_crd.yaml | 11 +++++++++++ 5 files changed, 24 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 5a2a869..ec10ab9 100644 --- a/Makefile +++ b/Makefile @@ -21,10 +21,10 @@ $(info ---- REGISTRY = $(REGISTRY)) CHART_NAME := redis-operator $(info ---- CHART_NAME = $(CHART_NAME)) -REDIS_TAG ?= 7.2.4-92 +REDIS_TAG ?= 7.4.2-54 $(info ---- REDIS_TAG = $(REDIS_TAG)) -OPERATOR_TAG ?= 7.2.4-12 +OPERATOR_TAG ?= 7.4.2-2 $(info ---- OPERATOR_TAG = $(OPERATOR_TAG)) # The repo to pull the operator image from Docker Hub registry. diff --git a/README.md b/README.md index 89c0c07..72030e4 100644 --- a/README.md +++ b/README.md @@ -91,8 +91,8 @@ Redis version tags are in the format Major.Minor.Patch-Sub but GKE Marketplace r ```shell export APP_INSTANCE_NAME=redis-enterprise-operator export NAMESPACE=redis -export TAG=7.2.4-12 -export DEPLOYER_TAG=7.2412 +export TAG=7.4.2-2 +export DEPLOYER_TAG=7.422 export REPO=gcr.io/cloud-marketplace/redislabs-public/redis-enterprise ``` diff --git a/chart/redis-operator/Chart.yaml b/chart/redis-operator/Chart.yaml index 6cff5a8..f2185a8 100644 --- a/chart/redis-operator/Chart.yaml +++ b/chart/redis-operator/Chart.yaml @@ -1,3 +1,3 @@ apiVersion: "v2" name: redis-operator -version: "1.27" +version: "1.28" diff --git a/deployer/rec_crd.yaml b/deployer/rec_crd.yaml index 4390f80..42a83b6 100644 --- a/deployer/rec_crd.yaml +++ b/deployer/rec_crd.yaml @@ -3664,6 +3664,12 @@ spec: - major - latest type: string + resp3Default: + description: Whether databases will turn on RESP3 compatibility upon + database upgrade. Note - Deleting this property after explicitly + setting its value shall have no effect. Please view the corresponding + field in RS doc for more info. + type: boolean serviceAccountName: description: Name of the service account to use type: string @@ -10919,6 +10925,8 @@ spec: - major - latest type: string + resp3Default: + type: boolean serviceAccountName: type: string servicesRiggerSpec: diff --git a/deployer/redb_crd.yaml b/deployer/redb_crd.yaml index 2449b5a..88b57e7 100644 --- a/deployer/redb_crd.yaml +++ b/deployer/redb_crd.yaml @@ -527,6 +527,12 @@ spec: - type type: object type: array + shardingEnabled: + description: Toggles database sharding for REAADBs (Active Active + databases) and enabled by default. This field is blocked for REDB + (non-Active Active databases) and sharding is toggled via the shardCount + field - when shardCount is 1 this is disabled otherwise enabled. + type: boolean shardCount: description: Number of database server-side shards type: integer @@ -604,6 +610,11 @@ spec: For password, use ''password'' as the key and the actual password as the value. Note that connections are not encrypted.' type: string + resp3: + description: Whether this database supports RESP3 protocol. + Note - Deleting this property after explicitly setting its value shall have no effect. + Please view the corresponding field in RS doc for more info. + type: boolean type: object status: description: RedisEnterpriseDatabaseStatus defines the observed state of From 23538c228cee2bcd8a3ef1cfc0235085cde92ec9 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 1 May 2024 08:07:18 +0000 Subject: [PATCH 115/120] Release version 7.4.2-12 --- Makefile | 4 ++-- README.md | 4 ++-- chart/redis-operator/Chart.yaml | 2 +- deployer/rec_crd.yaml | 35 ++++++++++++++++++++++++++++++++- deployer/redb_crd.yaml | 23 +++++++++++++++------- 5 files changed, 55 insertions(+), 13 deletions(-) diff --git a/Makefile b/Makefile index ec10ab9..8766d44 100644 --- a/Makefile +++ b/Makefile @@ -21,10 +21,10 @@ $(info ---- REGISTRY = $(REGISTRY)) CHART_NAME := redis-operator $(info ---- CHART_NAME = $(CHART_NAME)) -REDIS_TAG ?= 7.4.2-54 +REDIS_TAG ?= 6.4.2-43 $(info ---- REDIS_TAG = $(REDIS_TAG)) -OPERATOR_TAG ?= 7.4.2-2 +OPERATOR_TAG ?= 6.4.2-4 $(info ---- OPERATOR_TAG = $(OPERATOR_TAG)) # The repo to pull the operator image from Docker Hub registry. diff --git a/README.md b/README.md index 72030e4..bfc1b23 100644 --- a/README.md +++ b/README.md @@ -91,8 +91,8 @@ Redis version tags are in the format Major.Minor.Patch-Sub but GKE Marketplace r ```shell export APP_INSTANCE_NAME=redis-enterprise-operator export NAMESPACE=redis -export TAG=7.4.2-2 -export DEPLOYER_TAG=7.422 +export TAG=6.4.2-4 +export DEPLOYER_TAG=6.424 export REPO=gcr.io/cloud-marketplace/redislabs-public/redis-enterprise ``` diff --git a/chart/redis-operator/Chart.yaml b/chart/redis-operator/Chart.yaml index f2185a8..ce0c1e8 100644 --- a/chart/redis-operator/Chart.yaml +++ b/chart/redis-operator/Chart.yaml @@ -1,3 +1,3 @@ apiVersion: "v2" name: redis-operator -version: "1.28" +version: "1.25" diff --git a/deployer/rec_crd.yaml b/deployer/rec_crd.yaml index 42a83b6..c3336bd 100644 --- a/deployer/rec_crd.yaml +++ b/deployer/rec_crd.yaml @@ -150,6 +150,19 @@ spec: ingressOrRouteMethodStatus: description: The ingressOrRouteSpec/ActiveActive spec method that exist type: string + persistenceStatus: + description: The status of the Persistent Volume Claims that are used + for Redis Enterprise Cluster persistence. The status will correspond + to the status of one or more of the PVCs (failed/resizing if one of + them is in resize or failed to resize) + properties: + status: + description: The current status of the PVCs + type: string + succeeded: + description: The number of PVCs that are provisioned with the expected size + type: string + type: object redisEnterpriseIPFamily: type: string spec: @@ -677,18 +690,29 @@ spec: persistentSpec: description: Specification for Redis Enterprise Cluster persistence properties: + enablePersistentVolumeResize: + description: Whether to enable PersistentVolumes resize. Disabled + by default. Read the instruction in pvc_expansion readme carefully + before using this feature. + type: boolean enabled: description: Whether to add persistent volume to Redis Enterprise pods type: boolean storageClassName: description: Storage class for persistent volume in Redis Enterprise - pods Leave empty to use the default + pods. Leave empty to use the default. If using the default this + way, make sure the Kubernetes Cluster has a default Storage Class + configured. This can be done by running a `kubectl get storageclass` + and see if one of the Storage Classes' names contains a `(default)` + mark. type: string volumeSize: anyOf: - type: integer - type: string + description: To enable resizing after creating the cluster - please + follow the instructions in the pvc_expansion readme 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 @@ -7690,6 +7714,13 @@ spec: ldap: type: boolean type: object + persistenceStatus: + properties: + status: + type: string + succeeded: + type: string + type: object redisEnterpriseIPFamily: type: string spec: @@ -8027,6 +8058,8 @@ spec: type: integer persistentSpec: properties: + enablePersistentVolumeResize: + type: boolean enabled: type: boolean storageClassName: diff --git a/deployer/redb_crd.yaml b/deployer/redb_crd.yaml index 88b57e7..68cf16f 100644 --- a/deployer/redb_crd.yaml +++ b/deployer/redb_crd.yaml @@ -418,11 +418,10 @@ spec: description: The module's name e.g "ft" for redissearch type: string version: - description: Module's semantic version e.g "1.6.12" + description: Module's semantic version e.g "1.6.12" - optional only in REDB, must be set in REAADB type: string required: - name - - version type: object type: array ossCluster: @@ -505,7 +504,7 @@ spec: type: array replication: description: In-memory database replication. When enabled, database - will have replica shard for every master - leading to higher availability. + will have replica shard for every master - leading to higher availability. Defaults to false. type: boolean rolesPermissions: description: List of Redis Enteprise ACL and Role bindings to apply @@ -567,7 +566,9 @@ spec: size (RAM and Flash), as specified by "memorySize". type: string redisVersion: - description: Redis OSS version. For existing databases - Upgrade Redis + description: Redis OSS version. + Version can be specified via prefix, + or via channels - for existing databases - Upgrade Redis OSS version. For new databases - the version which the database will be created with. If set to 'major' - will always upgrade to the most recent major Redis version. If set to 'latest' - will always upgrade @@ -576,10 +577,18 @@ spec: must set redisUpgradePolicy on the cluster before. Possible values are 'major' or 'latest' When using upgrade - make sure to backup the database before. This value is used only for database type 'redis' - enum: - - major - - latest type: string + upgradeSpec: + description: Specifications for DB upgrade. + properties: + upgradeModulesToLatest: + description: Upgrades the modules to the latest version that supportes the DB version during a DB upgrade action, to upgrade the DB version view the 'redisVersion' field. + Notes - All modules must be without specifing the version. + in addition, This field is currently not supported for Active-Active databases. + type: boolean + required: + - upgradeModulesToLatest + type: object activeActive: description: Connection/ association to the Active-Active database. properties: From 6649daefc6cbc9f56971bcc7659a4d3017502d63 Mon Sep 17 00:00:00 2001 From: Zvi Cahana Date: Wed, 1 May 2024 12:35:11 +0300 Subject: [PATCH 116/120] Update version tags --- Makefile | 4 ++-- README.md | 4 ++-- chart/redis-operator/Chart.yaml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 8766d44..985bb99 100644 --- a/Makefile +++ b/Makefile @@ -21,10 +21,10 @@ $(info ---- REGISTRY = $(REGISTRY)) CHART_NAME := redis-operator $(info ---- CHART_NAME = $(CHART_NAME)) -REDIS_TAG ?= 6.4.2-43 +REDIS_TAG ?= 7.4.2-129 $(info ---- REDIS_TAG = $(REDIS_TAG)) -OPERATOR_TAG ?= 6.4.2-4 +OPERATOR_TAG ?= 7.4.2-12 $(info ---- OPERATOR_TAG = $(OPERATOR_TAG)) # The repo to pull the operator image from Docker Hub registry. diff --git a/README.md b/README.md index bfc1b23..94ea691 100644 --- a/README.md +++ b/README.md @@ -91,8 +91,8 @@ Redis version tags are in the format Major.Minor.Patch-Sub but GKE Marketplace r ```shell export APP_INSTANCE_NAME=redis-enterprise-operator export NAMESPACE=redis -export TAG=6.4.2-4 -export DEPLOYER_TAG=6.424 +export TAG=7.4.2-12 +export DEPLOYER_TAG=7.4212 export REPO=gcr.io/cloud-marketplace/redislabs-public/redis-enterprise ``` diff --git a/chart/redis-operator/Chart.yaml b/chart/redis-operator/Chart.yaml index ce0c1e8..d9a2c88 100644 --- a/chart/redis-operator/Chart.yaml +++ b/chart/redis-operator/Chart.yaml @@ -1,3 +1,3 @@ apiVersion: "v2" name: redis-operator -version: "1.25" +version: "1.29" From 07338ef5d865bba4b3494d8e88129cbd5100367f Mon Sep 17 00:00:00 2001 From: Zvi Cahana Date: Wed, 1 May 2024 12:36:38 +0300 Subject: [PATCH 117/120] Update REAADB CRD --- deployer/reaadb_crd.yaml | 48 ++++++++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/deployer/reaadb_crd.yaml b/deployer/reaadb_crd.yaml index 5382412..8c89e26 100644 --- a/deployer/reaadb_crd.yaml +++ b/deployer/reaadb_crd.yaml @@ -29,6 +29,9 @@ spec: - jsonPath: .status.linkedRedbs[*] name: Linked REDBs type: string + - jsonPath: .status.replicationStatus + name: Replication Status + type: string subresources: status: {} schema: @@ -69,6 +72,12 @@ spec: description: The corresponding ID of the instance in the active-active database. format: int64 type: integer + replicationStatus: + description: The replication status of the participating cluster + enum: + - up + - down + type: string required: - name type: object @@ -83,6 +92,12 @@ spec: description: The Redis Enterprise Cluster Object this Resource is associated with type: string + replicationStatus: + description: The overall replication status + enum: + - up + - down + type: string secretsStatus: description: The status of the secrets items: @@ -562,11 +577,10 @@ spec: nolint:staticcheck // custom json tag unknown to the linter type: string version: - description: Module's semantic version e.g "1.6.12" + description: Module's semantic version e.g "1.6.12" - optional only in REDB, must be set in REAADB type: string required: - name - - version type: object type: array ossCluster: @@ -605,7 +619,9 @@ spec: - name type: object redisVersion: - description: Redis OSS version. For existing databases - Upgrade + description: Redis OSS version. + Version can be specified via prefix, + or via channels - for existing databases - Upgrade Redis OSS version. For new databases - the version which the database will be created with. If set to 'major' - will always upgrade to the most recent major Redis version. If set to 'latest' @@ -615,10 +631,17 @@ spec: before. Possible values are 'major' or 'latest' When using upgrade - make sure to backup the database before. This value is used only for database type 'redis' - enum: - - major - - latest type: string + upgradeSpec: + description: Specifications for DB upgrade. + properties: + upgradeModulesToLatest: + description: Upgrades the modules to the latest version that supportes the DB version during a DB upgrade action, to upgrade the DB version view the 'redisVersion' field. + Note - This field is currently not supported for Active-Active databases. + type: boolean + required: + - upgradeModulesToLatest + type: object replicaSources: description: What databases to replicate from items: @@ -667,7 +690,12 @@ spec: replication: description: In-memory database replication. When enabled, database will have replica shard for every master - leading to higher - availability. + availability. Defaults to false. + type: boolean + resp3: + description: Whether this database supports RESP3 protocol. + Note - Deleting this property after explicitly setting its value shall have no effect. + Please view the corresponding field in RS doc for more info. type: boolean rofRamSize: description: The size of the RAM portion of an RoF database. Similarly @@ -700,6 +728,12 @@ spec: - type type: object type: array + shardingEnabled: + description: Toggles database sharding for REAADBs (Active Active + databases) and enabled by default. This field is blocked for REDB + (non-Active Active databases) and sharding is toggled via the shardCount + field - when shardCount is 1 this is disabled otherwise enabled. + type: boolean shardCount: description: Number of database server-side shards type: integer From 22310acde5f23c96e28948ea0ef7313c381ac126 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Tue, 30 Jul 2024 13:12:45 +0000 Subject: [PATCH 118/120] Release version 7.4.6-2 --- Makefile | 4 ++-- README.md | 4 ++-- chart/redis-operator/Chart.yaml | 2 +- deployer/reaadb_crd.yaml | 2 +- deployer/rec_crd.yaml | 25 +++++++++++++++++++++++++ 5 files changed, 31 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 985bb99..8766d44 100644 --- a/Makefile +++ b/Makefile @@ -21,10 +21,10 @@ $(info ---- REGISTRY = $(REGISTRY)) CHART_NAME := redis-operator $(info ---- CHART_NAME = $(CHART_NAME)) -REDIS_TAG ?= 7.4.2-129 +REDIS_TAG ?= 6.4.2-43 $(info ---- REDIS_TAG = $(REDIS_TAG)) -OPERATOR_TAG ?= 7.4.2-12 +OPERATOR_TAG ?= 6.4.2-4 $(info ---- OPERATOR_TAG = $(OPERATOR_TAG)) # The repo to pull the operator image from Docker Hub registry. diff --git a/README.md b/README.md index 94ea691..bfc1b23 100644 --- a/README.md +++ b/README.md @@ -91,8 +91,8 @@ Redis version tags are in the format Major.Minor.Patch-Sub but GKE Marketplace r ```shell export APP_INSTANCE_NAME=redis-enterprise-operator export NAMESPACE=redis -export TAG=7.4.2-12 -export DEPLOYER_TAG=7.4212 +export TAG=6.4.2-4 +export DEPLOYER_TAG=6.424 export REPO=gcr.io/cloud-marketplace/redislabs-public/redis-enterprise ``` diff --git a/chart/redis-operator/Chart.yaml b/chart/redis-operator/Chart.yaml index d9a2c88..ce0c1e8 100644 --- a/chart/redis-operator/Chart.yaml +++ b/chart/redis-operator/Chart.yaml @@ -1,3 +1,3 @@ apiVersion: "v2" name: redis-operator -version: "1.29" +version: "1.25" diff --git a/deployer/reaadb_crd.yaml b/deployer/reaadb_crd.yaml index 8c89e26..0c8fd8a 100644 --- a/deployer/reaadb_crd.yaml +++ b/deployer/reaadb_crd.yaml @@ -619,7 +619,7 @@ spec: - name type: object redisVersion: - description: Redis OSS version. + description: Redis OSS version. Version can be specified via prefix, or via channels - for existing databases - Upgrade Redis OSS version. For new databases - the version which the diff --git a/deployer/rec_crd.yaml b/deployer/rec_crd.yaml index c3336bd..97858e8 100644 --- a/deployer/rec_crd.yaml +++ b/deployer/rec_crd.yaml @@ -3694,6 +3694,21 @@ spec: setting its value shall have no effect. Please view the corresponding field in RS doc for more info. type: boolean + backup: + description: Cluster-wide backup configurations + properties: + s3: + description: Configurations for backups to s3 and s3-compatible + storage + properties: + caCertificateSecretName: + description: Secret name that holds the S3 CA certificate, which contains the TLS certificate mapped to the key in the secret 'cert' + type: string + url: + description: Specifies the URL for S3 export and import + type: string + type: object + type: object serviceAccountName: description: Name of the service account to use type: string @@ -10960,6 +10975,16 @@ spec: type: string resp3Default: type: boolean + backup: + properties: + s3: + properties: + caCertificateSecretName: + type: string + url: + type: string + type: object + type: object serviceAccountName: type: string servicesRiggerSpec: From 03d88c6e353e5f08d1c886d6b1d0ba7dad430502 Mon Sep 17 00:00:00 2001 From: Zvi Cahana Date: Tue, 30 Jul 2024 22:03:29 +0300 Subject: [PATCH 119/120] Update version tags --- Makefile | 4 ++-- README.md | 4 ++-- chart/redis-operator/Chart.yaml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 8766d44..3abb77f 100644 --- a/Makefile +++ b/Makefile @@ -21,10 +21,10 @@ $(info ---- REGISTRY = $(REGISTRY)) CHART_NAME := redis-operator $(info ---- CHART_NAME = $(CHART_NAME)) -REDIS_TAG ?= 6.4.2-43 +REDIS_TAG ?= 7.4.6-22 $(info ---- REDIS_TAG = $(REDIS_TAG)) -OPERATOR_TAG ?= 6.4.2-4 +OPERATOR_TAG ?= 7.4.6-2 $(info ---- OPERATOR_TAG = $(OPERATOR_TAG)) # The repo to pull the operator image from Docker Hub registry. diff --git a/README.md b/README.md index bfc1b23..283779a 100644 --- a/README.md +++ b/README.md @@ -91,8 +91,8 @@ Redis version tags are in the format Major.Minor.Patch-Sub but GKE Marketplace r ```shell export APP_INSTANCE_NAME=redis-enterprise-operator export NAMESPACE=redis -export TAG=6.4.2-4 -export DEPLOYER_TAG=6.424 +export TAG=7.4.6-2 +export DEPLOYER_TAG=7.462 export REPO=gcr.io/cloud-marketplace/redislabs-public/redis-enterprise ``` diff --git a/chart/redis-operator/Chart.yaml b/chart/redis-operator/Chart.yaml index ce0c1e8..7836b64 100644 --- a/chart/redis-operator/Chart.yaml +++ b/chart/redis-operator/Chart.yaml @@ -1,3 +1,3 @@ apiVersion: "v2" name: redis-operator -version: "1.25" +version: "1.30" From 2661b63db6647b30ae94912928e106114a954772 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Tue, 3 Dec 2024 15:13:13 +0000 Subject: [PATCH 120/120] Release version 7.8.2-6 --- Makefile | 4 ++-- README.md | 4 ++-- apptest/tester/Dockerfile | 10 ++++----- chart/redis-operator/Chart.yaml | 2 +- deployer/reaadb_crd.yaml | 14 ++++++++++-- deployer/rec_crd.yaml | 40 ++++++++++++++++++++++++++++++--- deployer/redb_crd.yaml | 32 ++++++++++++++++---------- deployer/rerc_crd.yaml | 7 ++++++ 8 files changed, 86 insertions(+), 27 deletions(-) diff --git a/Makefile b/Makefile index 3abb77f..bc0aa86 100644 --- a/Makefile +++ b/Makefile @@ -21,10 +21,10 @@ $(info ---- REGISTRY = $(REGISTRY)) CHART_NAME := redis-operator $(info ---- CHART_NAME = $(CHART_NAME)) -REDIS_TAG ?= 7.4.6-22 +REDIS_TAG ?= 7.8.2-34 $(info ---- REDIS_TAG = $(REDIS_TAG)) -OPERATOR_TAG ?= 7.4.6-2 +OPERATOR_TAG ?= 7.8.2-6 $(info ---- OPERATOR_TAG = $(OPERATOR_TAG)) # The repo to pull the operator image from Docker Hub registry. diff --git a/README.md b/README.md index 283779a..7f67dea 100644 --- a/README.md +++ b/README.md @@ -91,8 +91,8 @@ Redis version tags are in the format Major.Minor.Patch-Sub but GKE Marketplace r ```shell export APP_INSTANCE_NAME=redis-enterprise-operator export NAMESPACE=redis -export TAG=7.4.6-2 -export DEPLOYER_TAG=7.462 +export TAG=7.8.2-6 +export DEPLOYER_TAG=7.826 export REPO=gcr.io/cloud-marketplace/redislabs-public/redis-enterprise ``` diff --git a/apptest/tester/Dockerfile b/apptest/tester/Dockerfile index e8488a1..a700f79 100644 --- a/apptest/tester/Dockerfile +++ b/apptest/tester/Dockerfile @@ -1,18 +1,18 @@ -FROM gcr.io/cloud-marketplace-tools/testrunner:0.1.5 +FROM gcr.io/cloud-marketplace-tools/testrunner:0.1.7 ENV WAIT_FOR_READY_TIMEOUT 3600 ENV TESTER_TIMEOUT 3600 RUN apt-get update && \ apt-get install -y --no-install-recommends \ - curl wget dnsutils netcat jq \ + curl wget dnsutils netcat jq ca-certificates \ && rm -rf /var/lib/apt/lists/* -RUN curl -Lo /usr/local/bin/kubectl https://dl.k8s.io/release/v1.22.0/bin/linux/amd64/kubectl +RUN curl -Lo /usr/local/bin/kubectl https://dl.k8s.io/v1.31.3/bin/linux/amd64/kubectl RUN chmod +x /usr/local/bin/kubectl - + COPY tests/basic-suite.yaml /tests/basic-suite.yaml COPY tester.sh /tester.sh WORKDIR / -ENTRYPOINT ["/tester.sh"] +ENTRYPOINT ["/tester.sh"] \ No newline at end of file diff --git a/chart/redis-operator/Chart.yaml b/chart/redis-operator/Chart.yaml index 7836b64..bf3e5fc 100644 --- a/chart/redis-operator/Chart.yaml +++ b/chart/redis-operator/Chart.yaml @@ -1,3 +1,3 @@ apiVersion: "v2" name: redis-operator -version: "1.30" +version: "1.31" diff --git a/deployer/reaadb_crd.yaml b/deployer/reaadb_crd.yaml index 0c8fd8a..059628b 100644 --- a/deployer/reaadb_crd.yaml +++ b/deployer/reaadb_crd.yaml @@ -98,6 +98,13 @@ spec: - up - down type: string + clusterCertificatesGeneration: + description: Versions of the cluster's Proxy and Syncer certificates. + In Active-Active databases, these are used to detect updates to the + certificates, and trigger synchronization across the participating + clusters. . + format: int64 + type: integer secretsStatus: description: The status of the secrets items: @@ -171,7 +178,8 @@ spec: - participatingClusterName type: object alertSettings: - description: Settings for database alerts + description: Settings for database alerts. + Note - Alert settings are not supported for Active-Active database. properties: bdb_backup_delayed: description: Periodic backup has been delayed for longer than @@ -630,7 +638,9 @@ spec: for some databases, you must set redisUpgradePolicy on the cluster before. Possible values are 'major' or 'latest' When using upgrade - make sure to backup the database before. This value is used - only for database type 'redis' + only for database type 'redis'. + Note - Specifying Redis version is currently not + supported for Active-Active database. type: string upgradeSpec: description: Specifications for DB upgrade. diff --git a/deployer/rec_crd.yaml b/deployer/rec_crd.yaml index 97858e8..9aff2ea 100644 --- a/deployer/rec_crd.yaml +++ b/deployer/rec_crd.yaml @@ -147,6 +147,26 @@ spec: field in the RedisEnterpriseCluster resource. type: boolean type: object + certificatesStatus: + description: Stores information about cluster certificates and their + update process. In Active-Active databases, this is used to detect + updates to the certificates, and trigger synchronization across the + participating clusters. + properties: + generation: + description: Generation stores the version of the cluster's Proxy + and Syncer certificate secrets. In Active-Active databases, when + a user updates the proxy or syncer certificate, a crdb-update + command needs to be triggered to avoid potential sync issues. + This helps the REAADB controller detect a change in a certificate + and trigger a crdb-update. The version of the cluster's Proxy + certificate secret. + format: int64 + type: integer + updateStatus: + description: The status of the cluster's certificates update + type: string + type: object ingressOrRouteMethodStatus: description: The ingressOrRouteSpec/ActiveActive spec method that exist type: string @@ -595,6 +615,10 @@ spec: cacheTTLSeconds: description: The maximum TTL of cached entries. type: integer + directoryTimeoutSeconds: + description: The connection timeout to the LDAP server when authenticating + a user, in seconds + type: integer enabledForControlPlane: description: Whether to enable LDAP for control plane access. Disabled by default. @@ -865,7 +889,7 @@ spec: on Kubernetes v1.25+. Future versions of the RedisEnterpriseCluster API will remove support for this field altogether. For migration instructions, see https://kubernetes.io/docs/tasks/configure-pod-container/migrate-from-psp/ - \n Name of pod security policy to use on pods" + \n Name of pod security policy to use on pods" type: string podStartingPolicy: description: Mitigation setting for STS pods stuck in "ContainerCreating" @@ -6678,7 +6702,7 @@ spec: type: string required: - port - type: object + type: object httpGet: properties: host: @@ -7722,6 +7746,14 @@ spec: - version type: object type: array + certificatesStatus: + properties: + generation: + format: int64 + type: integer + updateStatus: + type: string + type: object ingressOrRouteMethodStatus: type: string managedAPIs: @@ -8019,6 +8051,8 @@ spec: type: string cacheTTLSeconds: type: integer + directoryTimeoutSeconds: + type: integer enabledForControlPlane: type: boolean enabledForDataPlane: @@ -13913,7 +13947,7 @@ spec: type: string required: - port - type: object + type: object httpGet: properties: host: diff --git a/deployer/redb_crd.yaml b/deployer/redb_crd.yaml index 68cf16f..4f20644 100644 --- a/deployer/redb_crd.yaml +++ b/deployer/redb_crd.yaml @@ -401,12 +401,16 @@ spec: and it must not be below 1GB. type: string modulesList: - description: List of modules associated with database. - Note - For Active-Active databases this feature is currently in preview. For - this feature to take effect for Active-Active databases, set a boolean environment variable - with the name "ENABLE_ALPHA_FEATURES" to True. This variable can - be set via the redis-enterprise-operator pod spec, or through the - operator-environment-config Config Map. + description: List of modules associated with database. Note - For Active-Active + databases this feature is currently in preview. For this feature to + take effect for Active-Active databases, set a boolean environment + variable with the name "ENABLE_ALPHA_FEATURES" to True. This variable + can be set via the redis-enterprise-operator pod spec, or through + the operator-environment-config Config Map. Note - if you do not want + to upgrade to the latest version you must set upgradeSpec -> upgradeModulesToLatest + to false. if you specify a version and do not set the upgradeModulesToLatest + it can result errors in the operator. in addition, the option to specify + specific version is Deprecated and will be deleted in next releases. items: description: 'Redis Enterprise Module: https://redislabs.com/redis-enterprise/modules/' properties: @@ -418,7 +422,8 @@ spec: description: The module's name e.g "ft" for redissearch type: string version: - description: Module's semantic version e.g "1.6.12" - optional only in REDB, must be set in REAADB + description: DEPRECATED - Module's semantic version e.g "1.6.12" + - optional only in REDB, must be set in REAADB type: string required: - name @@ -582,9 +587,12 @@ spec: description: Specifications for DB upgrade. properties: upgradeModulesToLatest: - description: Upgrades the modules to the latest version that supportes the DB version during a DB upgrade action, to upgrade the DB version view the 'redisVersion' field. - Notes - All modules must be without specifing the version. - in addition, This field is currently not supported for Active-Active databases. + description: DEPRECATED Upgrades the modules to the latest version + that supports the DB version during a DB upgrade action, to upgrade + the DB version view the 'redisVersion' field. Notes - All modules + must be without specifying the version. in addition, This field + is currently not supported for Active-Active databases. The default + is true type: boolean required: - upgradeModulesToLatest @@ -613,8 +621,8 @@ spec: - participatingClusterName type: object memcachedSaslSecretName: - description: 'Credentials used for binary authentication in memcached databases. - The credentials should be saved as an opaque secret and the name of that secret should be configured using this field. + description: 'Credentials used for binary authentication in memcached databases. + The credentials should be saved as an opaque secret and the name of that secret should be configured using this field. For username, use ''username'' as the key and the actual username as the value. For password, use ''password'' as the key and the actual password as the value. Note that connections are not encrypted.' diff --git a/deployer/rerc_crd.yaml b/deployer/rerc_crd.yaml index 344cef6..eab55df 100644 --- a/deployer/rerc_crd.yaml +++ b/deployer/rerc_crd.yaml @@ -56,6 +56,10 @@ spec: observedGeneration: description: The most recent generation observed for this RERC. It corresponds to the RERC's generation, which is updated by the API Server. type: integer + internalObservedSecretResourceVersion: + description: The observed secret resource version. + Used for internal purposes only. + type: string type: object spec: properties: @@ -67,6 +71,9 @@ spec: description: The database URL suffix, will be used for the active-active database replication endpoint and replication endpoint SNI. type: string + apiPort: + description: The port number of the cluster's URL used for connectivity/sync + type: integer recNamespace: description: The namespace of the REC that the RERC is pointing at type: string