From 8d56039ffd317656c98ac512bb3ebd553f6aee80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E9=93=AD?= Date: Tue, 11 Oct 2022 11:43:36 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BA=91=E5=8E=9F=E7=94=9F=E7=9A=84=20Research?= =?UTF-8?q?Ops/MLops=20=E6=B5=81=E6=B0=B4=E7=BA=BF=E5=B9=B3=E5=8F=B0?= =?UTF-8?q?=E6=9E=84=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ResearchOps/README.md | 184 + ResearchOps/applications/harbor.yml | 21 + .../applications/nfs-server-provisioner.yml | 23 + ResearchOps/applications/postgresql.yml | 21 + ResearchOps/argo-events/SA.yaml | 22 + ResearchOps/argo-events/eventbus-native.yaml | 10 + ResearchOps/argo-events/install.yaml | 404 + ResearchOps/argo-events/role_sensor.yaml | 15 + .../argo-events/rolebinding_sensor.yaml | 13 + ResearchOps/argo-events/sensor-rbac.yaml | 32 + ResearchOps/argo-events/webhook.yaml | 81 + ResearchOps/argo-events/workflow-rbac.yaml | 35 + ResearchOps/argo-workflows/Ingress.yaml | 27 + ResearchOps/argo-workflows/kustomization.yaml | 13 + ResearchOps/argo-workflows/manifest.yaml | 1163 ++ ResearchOps/argo-workflows/rolebinding.yml | 29 + .../workflow-controller-configmap.yaml | 27 + ResearchOps/argocd/ConfigMap.yaml | 10 + ResearchOps/argocd/Ingress.yaml | 26 + ResearchOps/argocd/kustomization.yaml | 13 + ResearchOps/argocd/manifest.yaml | 10712 ++++++++++++++++ ResearchOps/cert-manager/ClusterIssuer.yaml | 26 + ResearchOps/cert-manager/manifest.yaml | 5402 ++++++++ ResearchOps/cert-manager/namespace.yaml | 5 + ResearchOps/harbor/.helmignore | 6 + ResearchOps/harbor/Chart.yaml | 21 + ResearchOps/harbor/LICENSE | 201 + ResearchOps/harbor/README.md | 420 + ResearchOps/harbor/cert/tls.crt | 28 + ResearchOps/harbor/cert/tls.key | 51 + ResearchOps/harbor/conf/notary-server.json | 28 + ResearchOps/harbor/conf/notary-signer.json | 15 + ResearchOps/harbor/templates/Certificate.yaml | 14 + ResearchOps/harbor/templates/NOTES.txt | 3 + ResearchOps/harbor/templates/_helpers.tpl | 606 + .../templates/chartmuseum/chartmuseum-cm.yaml | 113 + .../chartmuseum/chartmuseum-dpl.yaml | 171 + .../chartmuseum/chartmuseum-pvc.yaml | 35 + .../chartmuseum/chartmuseum-secret.yaml | 26 + .../chartmuseum/chartmuseum-svc.yaml | 15 + .../chartmuseum/chartmuseum-tls.yaml | 15 + .../harbor/templates/core/core-cm.yaml | 70 + .../harbor/templates/core/core-dpl.yaml | 196 + .../templates/core/core-pre-upgrade-job.yaml | 66 + .../harbor/templates/core/core-secret.yaml | 19 + .../harbor/templates/core/core-svc.yaml | 21 + .../harbor/templates/core/core-tls.yaml | 15 + .../templates/database/database-secret.yaml | 11 + .../templates/database/database-ss.yaml | 160 + .../templates/database/database-svc.yaml | 14 + .../templates/exporter/exporter-cm-env.yaml | 35 + .../templates/exporter/exporter-dpl.yaml | 102 + .../templates/exporter/exporter-secret.yaml | 16 + .../templates/exporter/exporter-svc.yaml | 15 + .../harbor/templates/ingress/ingress.yaml | 209 + .../harbor/templates/ingress/secret.yaml | 15 + .../harbor/templates/internal/auto-tls.yaml | 98 + .../jobservice/jobservice-cm-env.yaml | 22 + .../templates/jobservice/jobservice-cm.yaml | 52 + .../templates/jobservice/jobservice-dpl.yaml | 143 + .../templates/jobservice/jobservice-pvc.yaml | 30 + .../jobservice/jobservice-secrets.yaml | 11 + .../templates/jobservice/jobservice-svc.yaml | 18 + .../templates/jobservice/jobservice-tls.yaml | 15 + .../templates/metrics/metrics-svcmon.yaml | 28 + .../templates/nginx/configmap-http.yaml | 148 + .../templates/nginx/configmap-https.yaml | 227 + .../harbor/templates/nginx/deployment.yaml | 109 + .../harbor/templates/nginx/secret.yaml | 23 + .../harbor/templates/nginx/service.yaml | 96 + .../templates/notary/notary-secret.yaml | 20 + .../templates/notary/notary-server.yaml | 108 + .../templates/notary/notary-signer.yaml | 102 + .../harbor/templates/notary/notary-svc.yaml | 31 + .../harbor/templates/portal/configmap.yaml | 60 + .../harbor/templates/portal/deployment.yaml | 96 + .../harbor/templates/portal/service.yaml | 16 + ResearchOps/harbor/templates/portal/tls.yaml | 15 + .../harbor/templates/redis/service.yaml | 14 + .../harbor/templates/redis/statefulset.yaml | 109 + .../templates/registry/registry-cm.yaml | 244 + .../templates/registry/registry-dpl.yaml | 281 + .../templates/registry/registry-pvc.yaml | 32 + .../templates/registry/registry-secret.yaml | 48 + .../templates/registry/registry-svc.yaml | 20 + .../templates/registry/registry-tls.yaml | 15 + .../templates/registry/registryctl-cm.yaml | 8 + .../registry/registryctl-secret.yaml | 9 + .../harbor/templates/trivy/trivy-secret.yaml | 12 + .../harbor/templates/trivy/trivy-sts.yaml | 204 + .../harbor/templates/trivy/trivy-svc.yaml | 16 + .../harbor/templates/trivy/trivy-tls.yaml | 15 + ResearchOps/harbor/values.yaml | 923 ++ ...7\346\236\266\346\236\204\345\233\276.svg" | 1 + ...3\351\241\271\346\212\245\345\221\212.pdf" | Bin 0 -> 4048205 bytes ResearchOps/mlops/EventSource.yaml | 34 + ResearchOps/mlops/Ingress.yaml | 26 + ResearchOps/mlops/Secret.yaml | 10 + ResearchOps/mlops/Sensor.yaml | 56 + ResearchOps/mlops/Workflow.yaml | 27 + ResearchOps/mlops/WorkflowTemplate.yaml | 239 + ResearchOps/mnist-basic/Ingress.yaml | 26 + .../mnist-basic/create-serviceaccounts.yaml | 126 + ResearchOps/mnist-basic/ns.yaml | 6 + ResearchOps/mnist-basic/secret.yaml | 8 + ResearchOps/mnist/mnist-train-eval.yaml | 114 + ResearchOps/nfs-server-provisioner/Chart.yaml | 8 + .../nfs-server-provisioner/values.yaml | 114 + ResearchOps/postgresql/Chart.yaml | 8 + 109 files changed, 25267 insertions(+) create mode 100755 ResearchOps/README.md create mode 100644 ResearchOps/applications/harbor.yml create mode 100644 ResearchOps/applications/nfs-server-provisioner.yml create mode 100644 ResearchOps/applications/postgresql.yml create mode 100644 ResearchOps/argo-events/SA.yaml create mode 100644 ResearchOps/argo-events/eventbus-native.yaml create mode 100644 ResearchOps/argo-events/install.yaml create mode 100644 ResearchOps/argo-events/role_sensor.yaml create mode 100644 ResearchOps/argo-events/rolebinding_sensor.yaml create mode 100644 ResearchOps/argo-events/sensor-rbac.yaml create mode 100644 ResearchOps/argo-events/webhook.yaml create mode 100644 ResearchOps/argo-events/workflow-rbac.yaml create mode 100644 ResearchOps/argo-workflows/Ingress.yaml create mode 100644 ResearchOps/argo-workflows/kustomization.yaml create mode 100644 ResearchOps/argo-workflows/manifest.yaml create mode 100644 ResearchOps/argo-workflows/rolebinding.yml create mode 100644 ResearchOps/argo-workflows/workflow-controller-configmap.yaml create mode 100644 ResearchOps/argocd/ConfigMap.yaml create mode 100644 ResearchOps/argocd/Ingress.yaml create mode 100644 ResearchOps/argocd/kustomization.yaml create mode 100644 ResearchOps/argocd/manifest.yaml create mode 100644 ResearchOps/cert-manager/ClusterIssuer.yaml create mode 100644 ResearchOps/cert-manager/manifest.yaml create mode 100644 ResearchOps/cert-manager/namespace.yaml create mode 100644 ResearchOps/harbor/.helmignore create mode 100644 ResearchOps/harbor/Chart.yaml create mode 100644 ResearchOps/harbor/LICENSE create mode 100644 ResearchOps/harbor/README.md create mode 100644 ResearchOps/harbor/cert/tls.crt create mode 100644 ResearchOps/harbor/cert/tls.key create mode 100644 ResearchOps/harbor/conf/notary-server.json create mode 100644 ResearchOps/harbor/conf/notary-signer.json create mode 100644 ResearchOps/harbor/templates/Certificate.yaml create mode 100644 ResearchOps/harbor/templates/NOTES.txt create mode 100644 ResearchOps/harbor/templates/_helpers.tpl create mode 100644 ResearchOps/harbor/templates/chartmuseum/chartmuseum-cm.yaml create mode 100644 ResearchOps/harbor/templates/chartmuseum/chartmuseum-dpl.yaml create mode 100644 ResearchOps/harbor/templates/chartmuseum/chartmuseum-pvc.yaml create mode 100644 ResearchOps/harbor/templates/chartmuseum/chartmuseum-secret.yaml create mode 100644 ResearchOps/harbor/templates/chartmuseum/chartmuseum-svc.yaml create mode 100644 ResearchOps/harbor/templates/chartmuseum/chartmuseum-tls.yaml create mode 100644 ResearchOps/harbor/templates/core/core-cm.yaml create mode 100644 ResearchOps/harbor/templates/core/core-dpl.yaml create mode 100644 ResearchOps/harbor/templates/core/core-pre-upgrade-job.yaml create mode 100644 ResearchOps/harbor/templates/core/core-secret.yaml create mode 100644 ResearchOps/harbor/templates/core/core-svc.yaml create mode 100644 ResearchOps/harbor/templates/core/core-tls.yaml create mode 100644 ResearchOps/harbor/templates/database/database-secret.yaml create mode 100644 ResearchOps/harbor/templates/database/database-ss.yaml create mode 100644 ResearchOps/harbor/templates/database/database-svc.yaml create mode 100644 ResearchOps/harbor/templates/exporter/exporter-cm-env.yaml create mode 100644 ResearchOps/harbor/templates/exporter/exporter-dpl.yaml create mode 100644 ResearchOps/harbor/templates/exporter/exporter-secret.yaml create mode 100644 ResearchOps/harbor/templates/exporter/exporter-svc.yaml create mode 100644 ResearchOps/harbor/templates/ingress/ingress.yaml create mode 100644 ResearchOps/harbor/templates/ingress/secret.yaml create mode 100644 ResearchOps/harbor/templates/internal/auto-tls.yaml create mode 100644 ResearchOps/harbor/templates/jobservice/jobservice-cm-env.yaml create mode 100644 ResearchOps/harbor/templates/jobservice/jobservice-cm.yaml create mode 100644 ResearchOps/harbor/templates/jobservice/jobservice-dpl.yaml create mode 100644 ResearchOps/harbor/templates/jobservice/jobservice-pvc.yaml create mode 100644 ResearchOps/harbor/templates/jobservice/jobservice-secrets.yaml create mode 100644 ResearchOps/harbor/templates/jobservice/jobservice-svc.yaml create mode 100644 ResearchOps/harbor/templates/jobservice/jobservice-tls.yaml create mode 100644 ResearchOps/harbor/templates/metrics/metrics-svcmon.yaml create mode 100644 ResearchOps/harbor/templates/nginx/configmap-http.yaml create mode 100644 ResearchOps/harbor/templates/nginx/configmap-https.yaml create mode 100644 ResearchOps/harbor/templates/nginx/deployment.yaml create mode 100644 ResearchOps/harbor/templates/nginx/secret.yaml create mode 100644 ResearchOps/harbor/templates/nginx/service.yaml create mode 100644 ResearchOps/harbor/templates/notary/notary-secret.yaml create mode 100644 ResearchOps/harbor/templates/notary/notary-server.yaml create mode 100644 ResearchOps/harbor/templates/notary/notary-signer.yaml create mode 100644 ResearchOps/harbor/templates/notary/notary-svc.yaml create mode 100644 ResearchOps/harbor/templates/portal/configmap.yaml create mode 100644 ResearchOps/harbor/templates/portal/deployment.yaml create mode 100644 ResearchOps/harbor/templates/portal/service.yaml create mode 100644 ResearchOps/harbor/templates/portal/tls.yaml create mode 100644 ResearchOps/harbor/templates/redis/service.yaml create mode 100644 ResearchOps/harbor/templates/redis/statefulset.yaml create mode 100644 ResearchOps/harbor/templates/registry/registry-cm.yaml create mode 100644 ResearchOps/harbor/templates/registry/registry-dpl.yaml create mode 100644 ResearchOps/harbor/templates/registry/registry-pvc.yaml create mode 100644 ResearchOps/harbor/templates/registry/registry-secret.yaml create mode 100644 ResearchOps/harbor/templates/registry/registry-svc.yaml create mode 100644 ResearchOps/harbor/templates/registry/registry-tls.yaml create mode 100644 ResearchOps/harbor/templates/registry/registryctl-cm.yaml create mode 100644 ResearchOps/harbor/templates/registry/registryctl-secret.yaml create mode 100644 ResearchOps/harbor/templates/trivy/trivy-secret.yaml create mode 100644 ResearchOps/harbor/templates/trivy/trivy-sts.yaml create mode 100644 ResearchOps/harbor/templates/trivy/trivy-svc.yaml create mode 100644 ResearchOps/harbor/templates/trivy/trivy-tls.yaml create mode 100644 ResearchOps/harbor/values.yaml create mode 100644 "ResearchOps/materials/images/\347\263\273\347\273\237\346\236\266\346\236\204\345\233\276.svg" create mode 100644 "ResearchOps/materials/\345\210\230\351\223\255-22b970388-\347\273\223\351\241\271\346\212\245\345\221\212.pdf" create mode 100644 ResearchOps/mlops/EventSource.yaml create mode 100644 ResearchOps/mlops/Ingress.yaml create mode 100644 ResearchOps/mlops/Secret.yaml create mode 100644 ResearchOps/mlops/Sensor.yaml create mode 100644 ResearchOps/mlops/Workflow.yaml create mode 100644 ResearchOps/mlops/WorkflowTemplate.yaml create mode 100644 ResearchOps/mnist-basic/Ingress.yaml create mode 100644 ResearchOps/mnist-basic/create-serviceaccounts.yaml create mode 100644 ResearchOps/mnist-basic/ns.yaml create mode 100644 ResearchOps/mnist-basic/secret.yaml create mode 100644 ResearchOps/mnist/mnist-train-eval.yaml create mode 100644 ResearchOps/nfs-server-provisioner/Chart.yaml create mode 100644 ResearchOps/nfs-server-provisioner/values.yaml create mode 100644 ResearchOps/postgresql/Chart.yaml diff --git a/ResearchOps/README.md b/ResearchOps/README.md new file mode 100755 index 0000000..14361c8 --- /dev/null +++ b/ResearchOps/README.md @@ -0,0 +1,184 @@ +# ResearchOps/Machine Learning Operations + +## Env + +- *.abu.pub is your managed domain + +- IP + +```shell +IP=`ifconfig eth0 | grep "inet " | awk '{print $2}'` +``` + +## Deploy Docker + +```shell +curl -fsSL https://get.docker.com -o get-docker.sh +sudo sh get-docker.sh +``` + +## Deploy Kubernetes + +```shell +curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION=v1.24.3+k3s1 sh -s - --advertise-address ${IP} --node-external-ip ${IP} +mkdir /root/.kube/ +ln -sf /etc/rancher/k3s/k3s.yaml /root/.kube/config +kubectl config set-cluster default --server=https://${IP}:6443 +apt update +apt install nfs-common -y +``` + +## Deploy cert-manager + +```shell +kubectl create -f cert-manager/namespace.yaml +kubectl create -f cert-manager/manifest.yaml +kubectl -n cert-manager rollout status deployment/cert-manager-webhook +``` + +- Add DNS record Use Cloudflare + +```text +Type: A +Name: * +IPv4 address: ${IP} +``` + +- Create Cloudflare Custom Token Use Cloudflare, and Modify cert-manager/ClusterIssuer.yaml, see Reference 2 + +```text +api-token: sZtljE0iuaNy1pb1veCv3jln_B85cRkZ8SPOROe_ +``` + +- Create Secret & ClusterIssuer + +```shell +kubectl create -f cert-manager/ClusterIssuer.yaml +``` + +## Deploy Argo CD + +```shell +kubectl apply -k argocd/ +kubectl -n argocd rollout status statefulset/argocd-application-controller +kubectl -n argocd rollout status deployment/argocd-repo-server +``` + +- Get Argo CD Password + +```shell +PASSWORD=`kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath='{.data.password}' | base64 -d` +echo "Complete. You should be able to navigate to https://argocd.abu.pub admin ${PASSWORD}" +``` + +## Deploy Workflow + +- Deploy + +```shell +kubectl create namespace argo +kubectl apply -k argo-workflows/ +kubectl -n argo rollout status deployment/workflow-controller +kubectl -n argo rollout status deployment/argo-server +echo "https://argo.abu.pub" +``` + +## Deploy NFS Server Provisioner + +```shell +kubectl delete sc local-path +kubectl create -f applications/nfs-server-provisioner.yml +kubectl -n nfs-server-provisioner rollout status statefulset/nfs-server-provisioner +``` + +## Deploy Postgres + +```shell +kubectl create -f applications/postgresql.yml +kubectl -n postgresql rollout status statefulset/postgresql-postgresql +export POSTGRES_PASSWORD=$(kubectl get secret --namespace postgresql postgresql -o jsonpath="{.data.postgresql-password}" | base64 --decode) +kubectl run postgresql-client --rm --tty -i --restart='Never' --namespace postgresql --image docker.io/bitnami/postgresql:11.14.0-debian-10-r28 --env="PGPASSWORD=$POSTGRES_PASSWORD" --command -- psql --host postgresql -U postgres -d postgres -p 5432 +create database registry; +create database notary_signer; +create database notary_server; +\l +\q +kubectl -n postgresql delete pods postgresql-client +echo ${POSTGRES_PASSWORD} +``` + +## Deploy Harbor + +> Modify Domain & PG Password in harbor/values.yaml & harbor/templates/Certificate.yaml + +```shell +kubectl create -f applications/harbor.yml +kubectl -n harbor rollout status deployment harbor-core +echo "https://harbor.abu.pub admin OpenSource@2022" +``` + +## Deploy Argo Events + +```shell +kubectl create namespace argo-events +kubectl create -f argo-events/install.yaml +kubectl create -n argo-events -f argo-events/eventbus-native.yaml +kubectl create -n argo-events -f argo-events/sensor-rbac.yaml +kubectl create -n argo-events -f argo-events/workflow-rbac.yaml +kubectl create -f argo-events/role_sensor.yaml +kubectl create -f argo-events/rolebinding_sensor.yaml +kubectl create -f argo-events/SA.yaml +kubectl create -n argo-events -f argo-events/webhook.yaml +kubectl -n argo-events port-forward $(kubectl -n argo-events get pod -l eventsource-name=webhook -o name) 12000:12000 & +curl -d '{"message":"this is my first webhook"}' -H "Content-Type: application/json" -X POST http://localhost:12000/example +kubectl -n argo-events get workflows +``` + +## Deploy MLOps WorkflowTemplate + +> Modify Domain in EventSource.yaml & Ingress.yaml + +```shell +kubectl create -f mlops/Secret.yaml +kubectl create -f mlops/EventSource.yaml +kubectl create -f mlops/Ingress.yaml +kubectl -n argo create secret docker-registry docek-harbor --docker-server=https://harbor.abu.pub --docker-username=admin --docker-password=OpenSource@2022 --docker-email=abuxliu@gmail.com +kubectl -n argo create -f mlops/WorkflowTemplate.yaml +``` + +## Deploy mnist Basic + +```shell +kubectl create -f mnist-basic/ns.yaml +kubectl -n mnist-demo create -f mnist-basic/secret.yaml +kubectl -n mnist-demo create -f mnist-basic/create-serviceaccounts.yaml +kubectl -n mnist-demo create -f mnist-basic/Ingress.yaml +``` + +## Deploy MLOps + +```shell +# Manual +kubectl -n argo create -f mlops/Workflow.yaml +# or Auto +kubectl create -f mlops/Sensor.yaml +``` + +## Again + +```shell +kubectl -n argocd delete applications mnist +``` + +## Reference + +- [Deploy Docker](https://docs.docker.com/engine/install/ubuntu/#install-using-the-convenience-script) + +- [Machine Learning Operations](https://ml-ops.org/) + +- [Cloudflare - cert-manager Documentation](https://cert-manager.io/docs/configuration/acme/dns01/cloudflare/) + +- [Argo Events Quick Start](https://argoproj.github.io/argo-events/quick_start/) + +- [Get GitHub Token](https://argoproj.github.io/argo-events/eventsources/setup/github/) + diff --git a/ResearchOps/applications/harbor.yml b/ResearchOps/applications/harbor.yml new file mode 100644 index 0000000..95a49fe --- /dev/null +++ b/ResearchOps/applications/harbor.yml @@ -0,0 +1,21 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: harbor + namespace: argocd +spec: + destination: + namespace: harbor + server: 'https://kubernetes.default.svc' + source: + path: argo-workflows-ci-example/harbor + repoURL: 'https://github.com/TECH4DX/MLops-Tools.git' + targetRevision: feature-integration + project: default + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - PrunePropagationPolicy=background + - CreateNamespace=true diff --git a/ResearchOps/applications/nfs-server-provisioner.yml b/ResearchOps/applications/nfs-server-provisioner.yml new file mode 100644 index 0000000..8db4143 --- /dev/null +++ b/ResearchOps/applications/nfs-server-provisioner.yml @@ -0,0 +1,23 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: nfs-server-provisioner + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + destination: + namespace: nfs-server-provisioner + server: 'https://kubernetes.default.svc' + source: + path: argo-workflows-ci-example/nfs-server-provisioner + repoURL: 'https://github.com/TECH4DX/MLops-Tools.git' + targetRevision: feature-integration + project: default + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - PrunePropagationPolicy=background + - CreateNamespace=true \ No newline at end of file diff --git a/ResearchOps/applications/postgresql.yml b/ResearchOps/applications/postgresql.yml new file mode 100644 index 0000000..1860f09 --- /dev/null +++ b/ResearchOps/applications/postgresql.yml @@ -0,0 +1,21 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: postgresql + namespace: argocd +spec: + destination: + namespace: postgresql + server: 'https://kubernetes.default.svc' + source: + path: argo-workflows-ci-example/postgresql + repoURL: 'https://github.com/TECH4DX/MLops-Tools.git' + targetRevision: feature-integration + project: default + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - PrunePropagationPolicy=background + - CreateNamespace=true \ No newline at end of file diff --git a/ResearchOps/argo-events/SA.yaml b/ResearchOps/argo-events/SA.yaml new file mode 100644 index 0000000..2ef1e8c --- /dev/null +++ b/ResearchOps/argo-events/SA.yaml @@ -0,0 +1,22 @@ +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: argo + namespace: argo-events +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: argo-binding-custome +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: argo-cluster-role +subjects: +- kind: ServiceAccount + name: argo + namespace: argo +- kind: ServiceAccount + name: argo + namespace: argo-events diff --git a/ResearchOps/argo-events/eventbus-native.yaml b/ResearchOps/argo-events/eventbus-native.yaml new file mode 100644 index 0000000..841a6b8 --- /dev/null +++ b/ResearchOps/argo-events/eventbus-native.yaml @@ -0,0 +1,10 @@ +apiVersion: argoproj.io/v1alpha1 +kind: EventBus +metadata: + name: default + namespace: argo-events +spec: + nats: + native: + replicas: 3 + auth: token diff --git a/ResearchOps/argo-events/install.yaml b/ResearchOps/argo-events/install.yaml new file mode 100644 index 0000000..a5d4703 --- /dev/null +++ b/ResearchOps/argo-events/install.yaml @@ -0,0 +1,404 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: eventbus.argoproj.io +spec: + group: argoproj.io + names: + kind: EventBus + listKind: EventBusList + plural: eventbus + shortNames: + - eb + singular: eventbus + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + type: object + x-kubernetes-preserve-unknown-fields: true + status: + type: object + x-kubernetes-preserve-unknown-fields: true + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: eventsources.argoproj.io +spec: + group: argoproj.io + names: + kind: EventSource + listKind: EventSourceList + plural: eventsources + shortNames: + - es + singular: eventsource + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + type: object + x-kubernetes-preserve-unknown-fields: true + status: + type: object + x-kubernetes-preserve-unknown-fields: true + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: sensors.argoproj.io +spec: + group: argoproj.io + names: + kind: Sensor + listKind: SensorList + plural: sensors + shortNames: + - sn + singular: sensor + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + type: object + x-kubernetes-preserve-unknown-fields: true + status: + type: object + x-kubernetes-preserve-unknown-fields: true + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: argo-events-sa + namespace: argo-events +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + rbac.authorization.k8s.io/aggregate-to-admin: "true" + name: argo-events-aggregate-to-admin +rules: +- apiGroups: + - argoproj.io + resources: + - sensors + - sensors/finalizers + - sensors/status + - eventsources + - eventsources/finalizers + - eventsources/status + - eventbus + - eventbus/finalizers + - eventbus/status + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + rbac.authorization.k8s.io/aggregate-to-edit: "true" + name: argo-events-aggregate-to-edit +rules: +- apiGroups: + - argoproj.io + resources: + - sensors + - sensors/finalizers + - sensors/status + - eventsources + - eventsources/finalizers + - eventsources/status + - eventbus + - eventbus/finalizers + - eventbus/status + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + rbac.authorization.k8s.io/aggregate-to-view: "true" + name: argo-events-aggregate-to-view +rules: +- apiGroups: + - argoproj.io + resources: + - sensors + - sensors/finalizers + - sensors/status + - eventsources + - eventsources/finalizers + - eventsources/status + - eventbus + - eventbus/finalizers + - eventbus/status + verbs: + - get + - list + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: argo-events-role +rules: +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - argoproj.io + resources: + - sensors + - sensors/finalizers + - sensors/status + - eventsources + - eventsources/finalizers + - eventsources/status + - eventbus + - eventbus/finalizers + - eventbus/status + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch +- apiGroups: + - "" + resources: + - pods + - pods/exec + - configmaps + - secrets + - services + - persistentvolumeclaims + verbs: + - create + - get + - list + - watch + - update + - patch + - delete +- apiGroups: + - apps + resources: + - deployments + - statefulsets + verbs: + - create + - get + - list + - watch + - update + - patch + - delete +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: argo-events-binding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: argo-events-role +subjects: +- kind: ServiceAccount + name: argo-events-sa + namespace: argo-events +--- +apiVersion: v1 +data: + controller-config.yaml: | + eventBus: + nats: + versions: + - version: 0.22.1 + natsStreamingImage: nats-streaming:0.22.1 + metricsExporterImage: natsio/prometheus-nats-exporter:0.8.0 + jetstream: + # Default JetStream settings, could be overridden by EventBus JetStream specs + settings: | + # https://docs.nats.io/running-a-nats-service/configuration#jetstream + # Only configure "max_memory_store" or "max_file_store", do not set "store_dir" as it has been hardcoded. + # e.g. 1G. -1 means no limit, up to 75% of available memory + max_memory_store: -1 + # e.g. 20G. -1 means no limit, Up to 1TB if available + max_file_store: 1TB + streamConfig: | + # The default properties of the streams to be created in this JetStream service + maxMsgs: 50000 + maxAge: 168h + maxBytes: -1 + replicas: 3 + duplicates: 300s + versions: + - version: latest + natsImage: nats:2.8.1 + metricsExporterImage: natsio/prometheus-nats-exporter:0.9.1 + configReloaderImage: natsio/nats-server-config-reloader:0.7.0 + startCommand: /nats-server + - version: 2.8.1 + natsImage: nats:2.8.1 + metricsExporterImage: natsio/prometheus-nats-exporter:0.9.1 + configReloaderImage: natsio/nats-server-config-reloader:0.7.0 + startCommand: /nats-server + - version: 2.8.1-alpine + natsImage: nats:2.8.1-alpine + metricsExporterImage: natsio/prometheus-nats-exporter:0.9.1 + configReloaderImage: natsio/nats-server-config-reloader:0.7.0 + startCommand: nats-server + - version: 2.8.2 + natsImage: nats:2.8.2 + metricsExporterImage: natsio/prometheus-nats-exporter:0.9.1 + configReloaderImage: natsio/nats-server-config-reloader:0.7.0 + startCommand: /nats-server + - version: 2.8.2-alpine + natsImage: nats:2.8.2-alpine + metricsExporterImage: natsio/prometheus-nats-exporter:0.9.1 + configReloaderImage: natsio/nats-server-config-reloader:0.7.0 + startCommand: nats-server +kind: ConfigMap +metadata: + name: argo-events-controller-config + namespace: argo-events +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: controller-manager + namespace: argo-events +spec: + replicas: 1 + selector: + matchLabels: + app: controller-manager + template: + metadata: + labels: + app: controller-manager + spec: + containers: + - args: + - controller + env: + - name: ARGO_EVENTS_IMAGE + value: quay.io/argoproj/argo-events:v1.7.1 + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + image: quay.io/argoproj/argo-events:v1.7.1 + imagePullPolicy: Always + livenessProbe: + httpGet: + path: /healthz + port: 8081 + initialDelaySeconds: 3 + periodSeconds: 3 + name: controller-manager + readinessProbe: + httpGet: + path: /readyz + port: 8081 + initialDelaySeconds: 3 + periodSeconds: 3 + volumeMounts: + - mountPath: /etc/argo-events + name: controller-config-volume + securityContext: + runAsNonRoot: true + runAsUser: 9731 + serviceAccountName: argo-events-sa + volumes: + - configMap: + name: argo-events-controller-config + name: controller-config-volume diff --git a/ResearchOps/argo-events/role_sensor.yaml b/ResearchOps/argo-events/role_sensor.yaml new file mode 100644 index 0000000..fcf873a --- /dev/null +++ b/ResearchOps/argo-events/role_sensor.yaml @@ -0,0 +1,15 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: operate-workflow-role + namespace: argo +rules: +- apiGroups: + - argoproj.io + resources: + - workflows + - workflowtemplates + - cronworkflows + - clusterworkflowtemplates + verbs: + - '*' diff --git a/ResearchOps/argo-events/rolebinding_sensor.yaml b/ResearchOps/argo-events/rolebinding_sensor.yaml new file mode 100644 index 0000000..b1f9a30 --- /dev/null +++ b/ResearchOps/argo-events/rolebinding_sensor.yaml @@ -0,0 +1,13 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: operate-workflow-role-binding + namespace: argo +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: operate-workflow-role +subjects: +- kind: ServiceAccount + name: operate-workflow-sa + namespace: argo-events diff --git a/ResearchOps/argo-events/sensor-rbac.yaml b/ResearchOps/argo-events/sensor-rbac.yaml new file mode 100644 index 0000000..012fd60 --- /dev/null +++ b/ResearchOps/argo-events/sensor-rbac.yaml @@ -0,0 +1,32 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: operate-workflow-sa +--- +# Similarly you can use a ClusterRole and ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: operate-workflow-role +rules: + - apiGroups: + - argoproj.io + verbs: + - "*" + resources: + - workflows + - workflowtemplates + - cronworkflows + - clusterworkflowtemplates +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: operate-workflow-role-binding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: operate-workflow-role +subjects: + - kind: ServiceAccount + name: operate-workflow-sa \ No newline at end of file diff --git a/ResearchOps/argo-events/webhook.yaml b/ResearchOps/argo-events/webhook.yaml new file mode 100644 index 0000000..6a01df0 --- /dev/null +++ b/ResearchOps/argo-events/webhook.yaml @@ -0,0 +1,81 @@ +apiVersion: argoproj.io/v1alpha1 +kind: EventSource +metadata: + name: webhook +spec: + service: + ports: + - port: 12000 + targetPort: 12000 + webhook: + # event-source can run multiple HTTP servers. Simply define a unique port to start a new HTTP server + example: + # port to run HTTP server on + port: "12000" + # endpoint to listen to + endpoint: /example + # HTTP request method to allow. In this case, only POST requests are accepted + method: POST + +# example-foo: +# port: "12000" +# endpoint: /example2 +# method: POST + +# Uncomment to use secure webhook +# example-secure: +# port: "13000" +# endpoint: "/secure" +# method: "POST" +# # k8s secret that contains the cert +# serverCertSecret: +# name: my-secret +# key: cert-key +# # k8s secret that contains the private key +# serverKeySecret: +# name: my-secret +# key: pk-key +--- +apiVersion: argoproj.io/v1alpha1 +kind: Sensor +metadata: + name: webhook +spec: + template: + serviceAccountName: operate-workflow-sa + dependencies: + - name: test-dep + eventSourceName: webhook + eventName: example + triggers: + - template: + name: webhook-workflow-trigger + k8s: + operation: create + source: + resource: + apiVersion: argoproj.io/v1alpha1 + kind: Workflow + metadata: + generateName: webhook- + spec: + entrypoint: whalesay + arguments: + parameters: + - name: message + # the value will get overridden by event payload from test-dep + value: hello world + templates: + - name: whalesay + inputs: + parameters: + - name: message + container: + image: docker/whalesay:latest + command: [cowsay] + args: ["{{inputs.parameters.message}}"] + parameters: + - src: + dependencyName: test-dep + dataKey: body + dest: spec.arguments.parameters.0.value \ No newline at end of file diff --git a/ResearchOps/argo-events/workflow-rbac.yaml b/ResearchOps/argo-events/workflow-rbac.yaml new file mode 100644 index 0000000..08bbfdc --- /dev/null +++ b/ResearchOps/argo-events/workflow-rbac.yaml @@ -0,0 +1,35 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: workflow-role +rules: + # pod get/watch is used to identify the container IDs of the current pod + # pod patch is used to annotate the step's outputs back to controller (e.g. artifact location) + - apiGroups: + - "" + resources: + - pods + verbs: + - get + - watch + - patch + # logs get/watch are used to get the pods logs for script outputs, and for log archival + - apiGroups: + - "" + resources: + - pods/log + verbs: + - get + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: workflow-role-binding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: workflow-role +subjects: + - kind: ServiceAccount + name: default \ No newline at end of file diff --git a/ResearchOps/argo-workflows/Ingress.yaml b/ResearchOps/argo-workflows/Ingress.yaml new file mode 100644 index 0000000..78e2f98 --- /dev/null +++ b/ResearchOps/argo-workflows/Ingress.yaml @@ -0,0 +1,27 @@ +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: argo-server-ingress + namespace: argo + annotations: + cert-manager.io/cluster-issuer: letsencrypt-dns01 + kubernetes.io/tls-acme: "true" + nginx.ingress.kubernetes.io/ssl-passthrough: "true" + nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" +spec: + rules: + - host: argo.abu.pub + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: argo-server + port: + name: web + tls: + - hosts: + - argo.abu.pub + secretName: argo-abu-pub-tls diff --git a/ResearchOps/argo-workflows/kustomization.yaml b/ResearchOps/argo-workflows/kustomization.yaml new file mode 100644 index 0000000..0bea111 --- /dev/null +++ b/ResearchOps/argo-workflows/kustomization.yaml @@ -0,0 +1,13 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: +- manifest.yaml +- rolebinding.yml +- Ingress.yaml + +patches: +- path: workflow-controller-configmap.yaml + target: + kind: ConfigMap + name: workflow-controller-configmap diff --git a/ResearchOps/argo-workflows/manifest.yaml b/ResearchOps/argo-workflows/manifest.yaml new file mode 100644 index 0000000..6ddfdd0 --- /dev/null +++ b/ResearchOps/argo-workflows/manifest.yaml @@ -0,0 +1,1163 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: clusterworkflowtemplates.argoproj.io +spec: + group: argoproj.io + names: + kind: ClusterWorkflowTemplate + listKind: ClusterWorkflowTemplateList + plural: clusterworkflowtemplates + shortNames: + - clusterwftmpl + - cwft + singular: clusterworkflowtemplate + scope: Cluster + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + type: object + x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true + required: + - metadata + - spec + type: object + served: true + storage: true +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: cronworkflows.argoproj.io +spec: + group: argoproj.io + names: + kind: CronWorkflow + listKind: CronWorkflowList + plural: cronworkflows + shortNames: + - cwf + - cronwf + singular: cronworkflow + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + type: object + x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true + status: + type: object + x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true + required: + - metadata + - spec + type: object + served: true + storage: true +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: workfloweventbindings.argoproj.io +spec: + group: argoproj.io + names: + kind: WorkflowEventBinding + listKind: WorkflowEventBindingList + plural: workfloweventbindings + shortNames: + - wfeb + singular: workfloweventbinding + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + type: object + x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true + required: + - metadata + - spec + type: object + served: true + storage: true +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: workflows.argoproj.io +spec: + group: argoproj.io + names: + kind: Workflow + listKind: WorkflowList + plural: workflows + shortNames: + - wf + singular: workflow + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: Status of the workflow + jsonPath: .status.phase + name: Status + type: string + - description: When the workflow was started + format: date-time + jsonPath: .status.startedAt + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + type: object + x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true + status: + type: object + x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: workflowtaskresults.argoproj.io +spec: + group: argoproj.io + names: + kind: WorkflowTaskResult + listKind: WorkflowTaskResultList + plural: workflowtaskresults + singular: workflowtaskresult + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + message: + type: string + metadata: + type: object + outputs: + properties: + artifacts: + items: + properties: + archive: + properties: + none: + type: object + tar: + properties: + compressionLevel: + format: int32 + type: integer + type: object + zip: + type: object + type: object + archiveLogs: + type: boolean + artifactory: + properties: + passwordSecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + url: + type: string + usernameSecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + required: + - url + type: object + from: + type: string + fromExpression: + type: string + gcs: + properties: + bucket: + type: string + key: + type: string + serviceAccountKeySecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + required: + - key + type: object + git: + properties: + depth: + format: int64 + type: integer + disableSubmodules: + type: boolean + fetch: + items: + type: string + type: array + insecureIgnoreHostKey: + type: boolean + passwordSecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + repo: + type: string + revision: + type: string + sshPrivateKeySecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + usernameSecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + required: + - repo + type: object + globalName: + type: string + hdfs: + properties: + addresses: + items: + type: string + type: array + force: + type: boolean + hdfsUser: + type: string + krbCCacheSecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + krbConfigConfigMap: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + krbKeytabSecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + krbRealm: + type: string + krbServicePrincipalName: + type: string + krbUsername: + type: string + path: + type: string + required: + - path + type: object + http: + properties: + headers: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + url: + type: string + required: + - url + type: object + mode: + format: int32 + type: integer + name: + type: string + optional: + type: boolean + oss: + properties: + accessKeySecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + bucket: + type: string + createBucketIfNotPresent: + type: boolean + endpoint: + type: string + key: + type: string + lifecycleRule: + properties: + markDeletionAfterDays: + format: int32 + type: integer + markInfrequentAccessAfterDays: + format: int32 + type: integer + type: object + secretKeySecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + securityToken: + type: string + required: + - key + type: object + path: + type: string + raw: + properties: + data: + type: string + required: + - data + type: object + recurseMode: + type: boolean + s3: + properties: + accessKeySecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + bucket: + type: string + createBucketIfNotPresent: + properties: + objectLocking: + type: boolean + type: object + encryptionOptions: + properties: + enableEncryption: + type: boolean + kmsEncryptionContext: + type: string + kmsKeyId: + type: string + serverSideCustomerKeySecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + type: object + endpoint: + type: string + insecure: + type: boolean + key: + type: string + region: + type: string + roleARN: + type: string + secretKeySecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + useSDKCreds: + type: boolean + type: object + subPath: + type: string + required: + - name + type: object + type: array + exitCode: + type: string + parameters: + items: + properties: + default: + type: string + description: + type: string + enum: + items: + type: string + type: array + globalName: + type: string + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + default: + type: string + event: + type: string + expression: + type: string + jqFilter: + type: string + jsonPath: + type: string + parameter: + type: string + path: + type: string + supplied: + type: object + type: object + required: + - name + type: object + type: array + result: + type: string + type: object + phase: + type: string + progress: + type: string + required: + - metadata + type: object + served: true + storage: true +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: workflowtasksets.argoproj.io +spec: + group: argoproj.io + names: + kind: WorkflowTaskSet + listKind: WorkflowTaskSetList + plural: workflowtasksets + shortNames: + - wfts + singular: workflowtaskset + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + type: object + x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true + status: + type: object + x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: workflowtemplates.argoproj.io +spec: + group: argoproj.io + names: + kind: WorkflowTemplate + listKind: WorkflowTemplateList + plural: workflowtemplates + shortNames: + - wftmpl + singular: workflowtemplate + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + type: object + x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true + required: + - metadata + - spec + type: object + served: true + storage: true +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: argo + namespace: argo +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: argo-server + namespace: argo +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: argo-role + namespace: argo +rules: +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create + - get + - update +- apiGroups: + - "" + resources: + - secrets + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + rbac.authorization.k8s.io/aggregate-to-admin: "true" + name: argo-aggregate-to-admin +rules: +- apiGroups: + - argoproj.io + resources: + - workflows + - workflows/finalizers + - workfloweventbindings + - workfloweventbindings/finalizers + - workflowtemplates + - workflowtemplates/finalizers + - cronworkflows + - cronworkflows/finalizers + - clusterworkflowtemplates + - clusterworkflowtemplates/finalizers + - workflowtasksets + - workflowtasksets/finalizers + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + rbac.authorization.k8s.io/aggregate-to-edit: "true" + name: argo-aggregate-to-edit +rules: +- apiGroups: + - argoproj.io + resources: + - workflows + - workflows/finalizers + - workfloweventbindings + - workfloweventbindings/finalizers + - workflowtemplates + - workflowtemplates/finalizers + - cronworkflows + - cronworkflows/finalizers + - clusterworkflowtemplates + - clusterworkflowtemplates/finalizers + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + rbac.authorization.k8s.io/aggregate-to-view: "true" + name: argo-aggregate-to-view +rules: +- apiGroups: + - argoproj.io + resources: + - workflows + - workflows/finalizers + - workfloweventbindings + - workfloweventbindings/finalizers + - workflowtemplates + - workflowtemplates/finalizers + - cronworkflows + - cronworkflows/finalizers + - clusterworkflowtemplates + - clusterworkflowtemplates/finalizers + verbs: + - get + - list + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: argo-cluster-role +rules: +- apiGroups: + - "" + resources: + - pods + - pods/exec + verbs: + - create + - get + - list + - watch + - update + - patch + - delete +- apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - watch + - list +- apiGroups: + - "" + resources: + - persistentvolumeclaims + - persistentvolumeclaims/finalizers + verbs: + - create + - update + - delete + - get +- apiGroups: + - argoproj.io + resources: + - workflows + - workflows/finalizers + - workflowtasksets + - workflowtasksets/finalizers + verbs: + - get + - list + - watch + - update + - patch + - delete + - create +- apiGroups: + - argoproj.io + resources: + - workflowtemplates + - workflowtemplates/finalizers + - clusterworkflowtemplates + - clusterworkflowtemplates/finalizers + verbs: + - get + - list + - watch +- apiGroups: + - argoproj.io + resources: + - workflowtaskresults + verbs: + - list + - watch + - deletecollection +- apiGroups: + - "" + resources: + - serviceaccounts + verbs: + - get + - list +- apiGroups: + - argoproj.io + resources: + - cronworkflows + - cronworkflows/finalizers + verbs: + - get + - list + - watch + - update + - patch + - delete +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +- apiGroups: + - policy + resources: + - poddisruptionbudgets + verbs: + - create + - get + - delete +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: argo-server-cluster-role +rules: +- apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - watch + - list +- apiGroups: + - "" + resources: + - secrets + verbs: + - get + - create + - list + - watch +- apiGroups: + - "" + resources: + - pods + - pods/exec + - pods/log + verbs: + - get + - list + - watch + - delete +- apiGroups: + - "" + resources: + - events + verbs: + - watch + - create + - patch +- apiGroups: + - "" + resources: + - serviceaccounts + verbs: + - get + - list + - watch +- apiGroups: + - argoproj.io + resources: + - eventsources + - sensors + - workflows + - workfloweventbindings + - workflowtemplates + - cronworkflows + - clusterworkflowtemplates + verbs: + - create + - get + - list + - watch + - update + - patch + - delete +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: argo-binding + namespace: argo +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: argo-role +subjects: +- kind: ServiceAccount + name: argo + namespace: argo +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: argo-binding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: argo-cluster-role +subjects: +- kind: ServiceAccount + name: argo + namespace: argo +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: argo-server-binding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: argo-server-cluster-role +subjects: +- kind: ServiceAccount + name: argo-server + namespace: argo +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: workflow-controller-configmap + namespace: argo +--- +apiVersion: v1 +kind: Service +metadata: + name: argo-server + namespace: argo +spec: + ports: + - name: web + port: 2746 + targetPort: 2746 + selector: + app: argo-server +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + workflows.argoproj.io/description: | + This service is deprecated. It will be removed in v3.4. + + https://github.com/argoproj/argo-workflows/issues/8441 + labels: + app: workflow-controller + name: workflow-controller-metrics + namespace: argo +spec: + ports: + - name: metrics + port: 9090 + protocol: TCP + targetPort: 9090 + selector: + app: workflow-controller +--- +apiVersion: scheduling.k8s.io/v1 +kind: PriorityClass +metadata: + name: workflow-controller +value: 1000000 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: argo-server + namespace: argo +spec: + selector: + matchLabels: + app: argo-server + template: + metadata: + labels: + app: argo-server + spec: + containers: + - args: + - server + - --auth-mode=server + env: + - name: BASE_HREF + value: / + - name: FIRST_TIME_USER_MODAL + value: "false" + - name: FEEDBACK_MODAL + value: "false" + - name: NEW_VERSION_MODAL + value: "false" + - name: ARGO_SECURE + value: "false" + image: quay.io/argoproj/argocli:v3.3.8 + name: argo-server + ports: + - containerPort: 2746 + name: web + readinessProbe: + httpGet: + path: / + port: 2746 + scheme: HTTP + initialDelaySeconds: 10 + periodSeconds: 20 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + volumeMounts: + - mountPath: /tmp + name: tmp + nodeSelector: + kubernetes.io/os: linux + securityContext: + runAsNonRoot: true + serviceAccountName: argo-server + volumes: + - emptyDir: {} + name: tmp +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: workflow-controller + namespace: argo +spec: + selector: + matchLabels: + app: workflow-controller + template: + metadata: + labels: + app: workflow-controller + spec: + containers: + - args: + - --configmap + - workflow-controller-configmap + - --executor-image + - quay.io/argoproj/argoexec:v3.3.8 + command: + - workflow-controller + env: + - name: LEADER_ELECTION_IDENTITY + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.name + image: quay.io/argoproj/workflow-controller:v3.3.8 + livenessProbe: + failureThreshold: 3 + httpGet: + path: /healthz + port: 6060 + initialDelaySeconds: 90 + periodSeconds: 60 + timeoutSeconds: 30 + name: workflow-controller + ports: + - containerPort: 9090 + name: metrics + - containerPort: 6060 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + nodeSelector: + kubernetes.io/os: linux + priorityClassName: workflow-controller + securityContext: + runAsNonRoot: true + serviceAccountName: argo diff --git a/ResearchOps/argo-workflows/rolebinding.yml b/ResearchOps/argo-workflows/rolebinding.yml new file mode 100644 index 0000000..8a5a4aa --- /dev/null +++ b/ResearchOps/argo-workflows/rolebinding.yml @@ -0,0 +1,29 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: argo-role + namespace: argocd +rules: +- apiGroups: + - argoproj.io + resources: + - applications + verbs: + - create + - get + - update + - delete +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: argo-binding + namespace: argocd +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: argo-role +subjects: +- kind: ServiceAccount + name: argo + namespace: argo diff --git a/ResearchOps/argo-workflows/workflow-controller-configmap.yaml b/ResearchOps/argo-workflows/workflow-controller-configmap.yaml new file mode 100644 index 0000000..a529300 --- /dev/null +++ b/ResearchOps/argo-workflows/workflow-controller-configmap.yaml @@ -0,0 +1,27 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: workflow-controller-configmap +data: + executor: | + imagePullPolicy: Always + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 500m + memory: 512Mi + workflowDefaults: | + spec: + # Time out after 1h + activeDeadlineSeconds: 3600 + # Delete (archive) workflows after 48h + ttlStrategy: + secondsAfterCompletion: 172800 + volumeClaimGC: + strategy: OnWorkflowCompletion + retryStrategy: + retryPolicy: OnError + limit: 3 + serviceAccountName: argo diff --git a/ResearchOps/argocd/ConfigMap.yaml b/ResearchOps/argocd/ConfigMap.yaml new file mode 100644 index 0000000..1356b44 --- /dev/null +++ b/ResearchOps/argocd/ConfigMap.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +data: + ui.bannercontent: "Machine Learning Operations" + ui.bannerpermanent: "true" +kind: ConfigMap +metadata: + labels: + app.kubernetes.io/name: argocd-cm + app.kubernetes.io/part-of: argocd + name: argocd-cm diff --git a/ResearchOps/argocd/Ingress.yaml b/ResearchOps/argocd/Ingress.yaml new file mode 100644 index 0000000..2295a45 --- /dev/null +++ b/ResearchOps/argocd/Ingress.yaml @@ -0,0 +1,26 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: argocd-server-ingress + namespace: argocd + annotations: + cert-manager.io/cluster-issuer: letsencrypt-dns01 + kubernetes.io/tls-acme: "true" + nginx.ingress.kubernetes.io/ssl-passthrough: "true" + nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" +spec: + rules: + - host: argocd.abu.pub + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: argocd-server + port: + name: http + tls: + - hosts: + - argocd.abu.pub + secretName: argocd-abu-pub-tls diff --git a/ResearchOps/argocd/kustomization.yaml b/ResearchOps/argocd/kustomization.yaml new file mode 100644 index 0000000..65b23b5 --- /dev/null +++ b/ResearchOps/argocd/kustomization.yaml @@ -0,0 +1,13 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: argocd + +resources: +- manifest.yaml +- Ingress.yaml + +patches: +- path: ConfigMap.yaml + target: + kind: ConfigMap + name: argocd-cm diff --git a/ResearchOps/argocd/manifest.yaml b/ResearchOps/argocd/manifest.yaml new file mode 100644 index 0000000..e22acde --- /dev/null +++ b/ResearchOps/argocd/manifest.yaml @@ -0,0 +1,10712 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: argocd +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + labels: + app.kubernetes.io/name: applications.argoproj.io + app.kubernetes.io/part-of: argocd + name: applications.argoproj.io +spec: + group: argoproj.io + names: + kind: Application + listKind: ApplicationList + plural: applications + shortNames: + - app + - apps + singular: application + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.sync.status + name: Sync Status + type: string + - jsonPath: .status.health.status + name: Health Status + type: string + - jsonPath: .status.sync.revision + name: Revision + priority: 10 + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: Application is a definition of Application resource. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + operation: + description: Operation contains information about a requested or running + operation + properties: + info: + description: Info is a list of informational items for this operation + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + initiatedBy: + description: InitiatedBy contains information about who initiated + the operations + properties: + automated: + description: Automated is set to true if operation was initiated + automatically by the application controller. + type: boolean + username: + description: Username contains the name of a user who started + operation + type: string + type: object + retry: + description: Retry controls the strategy to apply if a sync fails + properties: + backoff: + description: Backoff controls how to backoff on subsequent retries + of failed syncs + properties: + duration: + description: Duration is the amount to back off. Default unit + is seconds, but could also be a duration (e.g. "2m", "1h") + type: string + factor: + description: Factor is a factor to multiply the base duration + after each failed retry + format: int64 + type: integer + maxDuration: + description: MaxDuration is the maximum amount of time allowed + for the backoff strategy + type: string + type: object + limit: + description: Limit is the maximum number of attempts for retrying + a failed sync. If set to 0, no retries will be performed. + format: int64 + type: integer + type: object + sync: + description: Sync contains parameters for the operation + properties: + dryRun: + description: DryRun specifies to perform a `kubectl apply --dry-run` + without actually performing the sync + type: boolean + manifests: + description: Manifests is an optional field that overrides sync + source with a local directory for development + items: + type: string + type: array + prune: + description: Prune specifies to delete resources from the cluster + that are no longer tracked in git + type: boolean + resources: + description: Resources describes which resources shall be part + of the sync + items: + description: SyncOperationResource contains resources to sync. + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + required: + - kind + - name + type: object + type: array + revision: + description: Revision is the revision (Git) or chart version (Helm) + which to sync the application to If omitted, will use the revision + specified in app spec. + type: string + source: + description: Source overrides the source definition set in the + application. This is typically set in a Rollback operation and + is nil during a Sync operation + properties: + chart: + description: Chart is a Helm chart name, and must be specified + for applications sourced from a Helm repo. + type: string + directory: + description: Directory holds path/directory specific options + properties: + exclude: + description: Exclude contains a glob pattern to match + paths against that should be explicitly excluded from + being used during manifest generation + type: string + include: + description: Include contains a glob pattern to match + paths against that should be explicitly included during + manifest generation + type: string + jsonnet: + description: Jsonnet holds options specific to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet External + Variables + items: + description: JsonnetVar represents a variable to + be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level Arguments + items: + description: JsonnetVar represents a variable to + be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan a directory + recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters to the + helm template + items: + description: HelmFileParameter is a file parameter that's + passed to helm template during manifest generation + properties: + name: + description: Name is the name of the Helm parameter + type: string + path: + description: Path is the path to the file containing + the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents helm template + from failing when valueFiles do not exist locally by + not appending them to helm template --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters which + are passed to the helm template command upon manifest + generation + items: + description: HelmParameter is a parameter that's passed + to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether to tell + Helm to interpret booleans and numbers as strings + type: boolean + name: + description: Name is the name of the Helm parameter + type: string + value: + description: Value is the value for the Helm parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials to all domains + (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name to use. + If omitted it will use the application name + type: string + skipCrds: + description: SkipCrds skips custom resource definition + installation step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value files + to use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be passed + to helm template, typically defined as a block + type: string + version: + description: Version is the Helm version to use for templating + ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional + annotations to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional labels + to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies whether + to force applying common annotations to resources for + Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether to force + applying common labels to resources for Kustomize apps + type: boolean + images: + description: Images is a list of Kustomize image override + specifications + items: + description: KustomizeImage represents a Kustomize image + definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to resources + for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to resources + for Kustomize apps + type: string + version: + description: Version controls which version of Kustomize + to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git repository, + and is only valid for applications sourced from Git. + type: string + plugin: + description: ConfigManagementPlugin holds config management + plugin specific options + properties: + env: + description: Env is a list of environment variable entries + items: + description: EnvEntry represents an entry in the application's + environment + properties: + name: + description: Name is the name of the variable, usually + expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + type: object + repoURL: + description: RepoURL is the URL to the repository (Git or + Helm) that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of the source + to sync the application to. In case of Git, this can be + commit, tag, or branch. If omitted, will equal to HEAD. + In case of Helm, this is a semver tag for the Chart's version. + type: string + required: + - repoURL + type: object + syncOptions: + description: SyncOptions provide per-sync sync-options, e.g. Validate=false + items: + type: string + type: array + syncStrategy: + description: SyncStrategy describes how to perform the sync + properties: + apply: + description: Apply will perform a `kubectl apply` to perform + the sync. + properties: + force: + description: Force indicates whether or not to supply + the --force flag to `kubectl apply`. The --force flag + deletes and re-create the resource, when PATCH encounters + conflict and has retried for 5 times. + type: boolean + type: object + hook: + description: Hook will submit any referenced resources to + perform the sync. This is the default strategy + properties: + force: + description: Force indicates whether or not to supply + the --force flag to `kubectl apply`. The --force flag + deletes and re-create the resource, when PATCH encounters + conflict and has retried for 5 times. + type: boolean + type: object + type: object + type: object + type: object + spec: + description: ApplicationSpec represents desired application state. Contains + link to repository with application definition and additional parameters + link definition revision. + properties: + destination: + description: Destination is a reference to the target Kubernetes server + and namespace + properties: + name: + description: Name is an alternate way of specifying the target + cluster by its symbolic name + type: string + namespace: + description: Namespace specifies the target namespace for the + application's resources. The namespace will only be set for + namespace-scoped resources that have not set a value for .metadata.namespace + type: string + server: + description: Server specifies the URL of the target cluster and + must be set to the Kubernetes control plane API + type: string + type: object + ignoreDifferences: + description: IgnoreDifferences is a list of resources and their fields + which should be ignored during comparison + items: + description: ResourceIgnoreDifferences contains resource filter + and list of json paths which should be ignored during comparison + with live state. + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + description: ManagedFieldsManagers is a list of trusted managers. + Fields mutated by those managers will take precedence over + the desired state defined in the SCM and won't be displayed + in diffs + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + description: Info contains a list of information (URLs, email addresses, + and plain text) that relates to the application + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + description: Project is a reference to the project this application + belongs to. The empty string means that application belongs to the + 'default' project. + type: string + revisionHistoryLimit: + description: RevisionHistoryLimit limits the number of items kept + in the application's revision history, which is used for informational + purposes as well as for rollbacks to previous versions. This should + only be changed in exceptional circumstances. Setting to zero will + store no history. This will reduce storage used. Increasing will + increase the space used to store the history, so we do not recommend + increasing it. Default is 10. + format: int64 + type: integer + source: + description: Source is a reference to the location of the application's + manifests or chart + properties: + chart: + description: Chart is a Helm chart name, and must be specified + for applications sourced from a Helm repo. + type: string + directory: + description: Directory holds path/directory specific options + properties: + exclude: + description: Exclude contains a glob pattern to match paths + against that should be explicitly excluded from being used + during manifest generation + type: string + include: + description: Include contains a glob pattern to match paths + against that should be explicitly included during manifest + generation + type: string + jsonnet: + description: Jsonnet holds options specific to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet External Variables + items: + description: JsonnetVar represents a variable to be + passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level Arguments + items: + description: JsonnetVar represents a variable to be + passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan a directory + recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters to the helm + template + items: + description: HelmFileParameter is a file parameter that's + passed to helm template during manifest generation + properties: + name: + description: Name is the name of the Helm parameter + type: string + path: + description: Path is the path to the file containing + the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents helm template + from failing when valueFiles do not exist locally by not + appending them to helm template --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters which + are passed to the helm template command upon manifest generation + items: + description: HelmParameter is a parameter that's passed + to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether to tell + Helm to interpret booleans and numbers as strings + type: boolean + name: + description: Name is the name of the Helm parameter + type: string + value: + description: Value is the value for the Helm parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials to all domains + (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name to use. + If omitted it will use the application name + type: string + skipCrds: + description: SkipCrds skips custom resource definition installation + step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value files to + use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be passed to + helm template, typically defined as a block + type: string + version: + description: Version is the Helm version to use for templating + ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional annotations + to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional labels to + add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies whether to force + applying common annotations to resources for Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether to force + applying common labels to resources for Kustomize apps + type: boolean + images: + description: Images is a list of Kustomize image override + specifications + items: + description: KustomizeImage represents a Kustomize image + definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to resources + for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to resources + for Kustomize apps + type: string + version: + description: Version controls which version of Kustomize to + use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git repository, + and is only valid for applications sourced from Git. + type: string + plugin: + description: ConfigManagementPlugin holds config management plugin + specific options + properties: + env: + description: Env is a list of environment variable entries + items: + description: EnvEntry represents an entry in the application's + environment + properties: + name: + description: Name is the name of the variable, usually + expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + type: object + repoURL: + description: RepoURL is the URL to the repository (Git or Helm) + that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of the source + to sync the application to. In case of Git, this can be commit, + tag, or branch. If omitted, will equal to HEAD. In case of Helm, + this is a semver tag for the Chart's version. + type: string + required: + - repoURL + type: object + syncPolicy: + description: SyncPolicy controls when and how a sync will be performed + properties: + automated: + description: Automated will keep an application synced to the + target revision + properties: + allowEmpty: + description: 'AllowEmpty allows apps have zero live resources + (default: false)' + type: boolean + prune: + description: 'Prune specifies whether to delete resources + from the cluster that are not found in the sources anymore + as part of automated sync (default: false)' + type: boolean + selfHeal: + description: 'SelfHeal specifes whether to revert resources + back to their desired state upon modification in the cluster + (default: false)' + type: boolean + type: object + retry: + description: Retry controls failed sync retry behavior + properties: + backoff: + description: Backoff controls how to backoff on subsequent + retries of failed syncs + properties: + duration: + description: Duration is the amount to back off. Default + unit is seconds, but could also be a duration (e.g. + "2m", "1h") + type: string + factor: + description: Factor is a factor to multiply the base duration + after each failed retry + format: int64 + type: integer + maxDuration: + description: MaxDuration is the maximum amount of time + allowed for the backoff strategy + type: string + type: object + limit: + description: Limit is the maximum number of attempts for retrying + a failed sync. If set to 0, no retries will be performed. + format: int64 + type: integer + type: object + syncOptions: + description: Options allow you to specify whole app sync-options + items: + type: string + type: array + type: object + required: + - destination + - project + - source + type: object + status: + description: ApplicationStatus contains status information for the application + properties: + conditions: + description: Conditions is a list of currently observed application + conditions + items: + description: ApplicationCondition contains details about an application + condition, which is usally an error or warning + properties: + lastTransitionTime: + description: LastTransitionTime is the time the condition was + last observed + format: date-time + type: string + message: + description: Message contains human-readable message indicating + details about condition + type: string + type: + description: Type is an application condition type + type: string + required: + - message + - type + type: object + type: array + health: + description: Health contains information about the application's current + health status + properties: + message: + description: Message is a human-readable informational message + describing the health status + type: string + status: + description: Status holds the status code of the application or + resource + type: string + type: object + history: + description: History contains information about the application's + sync history + items: + description: RevisionHistory contains history information about + a previous sync + properties: + deployStartedAt: + description: DeployStartedAt holds the time the sync operation + started + format: date-time + type: string + deployedAt: + description: DeployedAt holds the time the sync operation completed + format: date-time + type: string + id: + description: ID is an auto incrementing identifier of the RevisionHistory + format: int64 + type: integer + revision: + description: Revision holds the revision the sync was performed + against + type: string + source: + description: Source is a reference to the application source + used for the sync operation + properties: + chart: + description: Chart is a Helm chart name, and must be specified + for applications sourced from a Helm repo. + type: string + directory: + description: Directory holds path/directory specific options + properties: + exclude: + description: Exclude contains a glob pattern to match + paths against that should be explicitly excluded from + being used during manifest generation + type: string + include: + description: Include contains a glob pattern to match + paths against that should be explicitly included during + manifest generation + type: string + jsonnet: + description: Jsonnet holds options specific to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet External + Variables + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level + Arguments + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan a directory + recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters to the + helm template + items: + description: HelmFileParameter is a file parameter + that's passed to helm template during manifest generation + properties: + name: + description: Name is the name of the Helm parameter + type: string + path: + description: Path is the path to the file containing + the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents helm template + from failing when valueFiles do not exist locally + by not appending them to helm template --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters + which are passed to the helm template command upon + manifest generation + items: + description: HelmParameter is a parameter that's passed + to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether to + tell Helm to interpret booleans and numbers + as strings + type: boolean + name: + description: Name is the name of the Helm parameter + type: string + value: + description: Value is the value for the Helm parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials to all + domains (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name to + use. If omitted it will use the application name + type: string + skipCrds: + description: SkipCrds skips custom resource definition + installation step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value files + to use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be passed + to helm template, typically defined as a block + type: string + version: + description: Version is the Helm version to use for + templating ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional + annotations to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional labels + to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies whether + to force applying common annotations to resources + for Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether to + force applying common labels to resources for Kustomize + apps + type: boolean + images: + description: Images is a list of Kustomize image override + specifications + items: + description: KustomizeImage represents a Kustomize + image definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to resources + for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to resources + for Kustomize apps + type: string + version: + description: Version controls which version of Kustomize + to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git repository, + and is only valid for applications sourced from Git. + type: string + plugin: + description: ConfigManagementPlugin holds config management + plugin specific options + properties: + env: + description: Env is a list of environment variable entries + items: + description: EnvEntry represents an entry in the application's + environment + properties: + name: + description: Name is the name of the variable, + usually expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + type: object + repoURL: + description: RepoURL is the URL to the repository (Git or + Helm) that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of the + source to sync the application to. In case of Git, this + can be commit, tag, or branch. If omitted, will equal + to HEAD. In case of Helm, this is a semver tag for the + Chart's version. + type: string + required: + - repoURL + type: object + required: + - deployedAt + - id + - revision + type: object + type: array + observedAt: + description: 'ObservedAt indicates when the application state was + updated without querying latest git state Deprecated: controller + no longer updates ObservedAt field' + format: date-time + type: string + operationState: + description: OperationState contains information about any ongoing + operations, such as a sync + properties: + finishedAt: + description: FinishedAt contains time of operation completion + format: date-time + type: string + message: + description: Message holds any pertinent messages when attempting + to perform operation (typically errors). + type: string + operation: + description: Operation is the original requested operation + properties: + info: + description: Info is a list of informational items for this + operation + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + initiatedBy: + description: InitiatedBy contains information about who initiated + the operations + properties: + automated: + description: Automated is set to true if operation was + initiated automatically by the application controller. + type: boolean + username: + description: Username contains the name of a user who + started operation + type: string + type: object + retry: + description: Retry controls the strategy to apply if a sync + fails + properties: + backoff: + description: Backoff controls how to backoff on subsequent + retries of failed syncs + properties: + duration: + description: Duration is the amount to back off. Default + unit is seconds, but could also be a duration (e.g. + "2m", "1h") + type: string + factor: + description: Factor is a factor to multiply the base + duration after each failed retry + format: int64 + type: integer + maxDuration: + description: MaxDuration is the maximum amount of + time allowed for the backoff strategy + type: string + type: object + limit: + description: Limit is the maximum number of attempts for + retrying a failed sync. If set to 0, no retries will + be performed. + format: int64 + type: integer + type: object + sync: + description: Sync contains parameters for the operation + properties: + dryRun: + description: DryRun specifies to perform a `kubectl apply + --dry-run` without actually performing the sync + type: boolean + manifests: + description: Manifests is an optional field that overrides + sync source with a local directory for development + items: + type: string + type: array + prune: + description: Prune specifies to delete resources from + the cluster that are no longer tracked in git + type: boolean + resources: + description: Resources describes which resources shall + be part of the sync + items: + description: SyncOperationResource contains resources + to sync. + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + required: + - kind + - name + type: object + type: array + revision: + description: Revision is the revision (Git) or chart version + (Helm) which to sync the application to If omitted, + will use the revision specified in app spec. + type: string + source: + description: Source overrides the source definition set + in the application. This is typically set in a Rollback + operation and is nil during a Sync operation + properties: + chart: + description: Chart is a Helm chart name, and must + be specified for applications sourced from a Helm + repo. + type: string + directory: + description: Directory holds path/directory specific + options + properties: + exclude: + description: Exclude contains a glob pattern to + match paths against that should be explicitly + excluded from being used during manifest generation + type: string + include: + description: Include contains a glob pattern to + match paths against that should be explicitly + included during manifest generation + type: string + jsonnet: + description: Jsonnet holds options specific to + Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet + External Variables + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest + generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level + Arguments + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest + generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan + a directory recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters + to the helm template + items: + description: HelmFileParameter is a file parameter + that's passed to helm template during manifest + generation + properties: + name: + description: Name is the name of the Helm + parameter + type: string + path: + description: Path is the path to the file + containing the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents + helm template from failing when valueFiles do + not exist locally by not appending them to helm + template --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters + which are passed to the helm template command + upon manifest generation + items: + description: HelmParameter is a parameter that's + passed to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether + to tell Helm to interpret booleans and + numbers as strings + type: boolean + name: + description: Name is the name of the Helm + parameter + type: string + value: + description: Value is the value for the + Helm parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials + to all domains (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name + to use. If omitted it will use the application + name + type: string + skipCrds: + description: SkipCrds skips custom resource definition + installation step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value + files to use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be + passed to helm template, typically defined as + a block + type: string + version: + description: Version is the Helm version to use + for templating ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional + annotations to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional + labels to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies + whether to force applying common annotations + to resources for Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether + to force applying common labels to resources + for Kustomize apps + type: boolean + images: + description: Images is a list of Kustomize image + override specifications + items: + description: KustomizeImage represents a Kustomize + image definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to + resources for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to + resources for Kustomize apps + type: string + version: + description: Version controls which version of + Kustomize to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git + repository, and is only valid for applications sourced + from Git. + type: string + plugin: + description: ConfigManagementPlugin holds config management + plugin specific options + properties: + env: + description: Env is a list of environment variable + entries + items: + description: EnvEntry represents an entry in + the application's environment + properties: + name: + description: Name is the name of the variable, + usually expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + type: object + repoURL: + description: RepoURL is the URL to the repository + (Git or Helm) that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of + the source to sync the application to. In case of + Git, this can be commit, tag, or branch. If omitted, + will equal to HEAD. In case of Helm, this is a semver + tag for the Chart's version. + type: string + required: + - repoURL + type: object + syncOptions: + description: SyncOptions provide per-sync sync-options, + e.g. Validate=false + items: + type: string + type: array + syncStrategy: + description: SyncStrategy describes how to perform the + sync + properties: + apply: + description: Apply will perform a `kubectl apply` + to perform the sync. + properties: + force: + description: Force indicates whether or not to + supply the --force flag to `kubectl apply`. + The --force flag deletes and re-create the resource, + when PATCH encounters conflict and has retried + for 5 times. + type: boolean + type: object + hook: + description: Hook will submit any referenced resources + to perform the sync. This is the default strategy + properties: + force: + description: Force indicates whether or not to + supply the --force flag to `kubectl apply`. + The --force flag deletes and re-create the resource, + when PATCH encounters conflict and has retried + for 5 times. + type: boolean + type: object + type: object + type: object + type: object + phase: + description: Phase is the current phase of the operation + type: string + retryCount: + description: RetryCount contains time of operation retries + format: int64 + type: integer + startedAt: + description: StartedAt contains time of operation start + format: date-time + type: string + syncResult: + description: SyncResult is the result of a Sync operation + properties: + resources: + description: Resources contains a list of sync result items + for each individual resource in a sync operation + items: + description: ResourceResult holds the operation result details + of a specific resource + properties: + group: + description: Group specifies the API group of the resource + type: string + hookPhase: + description: HookPhase contains the state of any operation + associated with this resource OR hook This can also + contain values for non-hook resources. + type: string + hookType: + description: HookType specifies the type of the hook. + Empty for non-hook resources + type: string + kind: + description: Kind specifies the API kind of the resource + type: string + message: + description: Message contains an informational or error + message for the last sync OR operation + type: string + name: + description: Name specifies the name of the resource + type: string + namespace: + description: Namespace specifies the target namespace + of the resource + type: string + status: + description: Status holds the final result of the sync. + Will be empty if the resources is yet to be applied/pruned + and is always zero-value for hooks + type: string + syncPhase: + description: SyncPhase indicates the particular phase + of the sync that this result was acquired in + type: string + version: + description: Version specifies the API version of the + resource + type: string + required: + - group + - kind + - name + - namespace + - version + type: object + type: array + revision: + description: Revision holds the revision this sync operation + was performed to + type: string + source: + description: Source records the application source information + of the sync, used for comparing auto-sync + properties: + chart: + description: Chart is a Helm chart name, and must be specified + for applications sourced from a Helm repo. + type: string + directory: + description: Directory holds path/directory specific options + properties: + exclude: + description: Exclude contains a glob pattern to match + paths against that should be explicitly excluded + from being used during manifest generation + type: string + include: + description: Include contains a glob pattern to match + paths against that should be explicitly included + during manifest generation + type: string + jsonnet: + description: Jsonnet holds options specific to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet External + Variables + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level + Arguments + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan a directory + recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters to + the helm template + items: + description: HelmFileParameter is a file parameter + that's passed to helm template during manifest + generation + properties: + name: + description: Name is the name of the Helm parameter + type: string + path: + description: Path is the path to the file containing + the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents helm + template from failing when valueFiles do not exist + locally by not appending them to helm template --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters + which are passed to the helm template command upon + manifest generation + items: + description: HelmParameter is a parameter that's + passed to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether + to tell Helm to interpret booleans and numbers + as strings + type: boolean + name: + description: Name is the name of the Helm parameter + type: string + value: + description: Value is the value for the Helm + parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials to all + domains (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name + to use. If omitted it will use the application name + type: string + skipCrds: + description: SkipCrds skips custom resource definition + installation step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value files + to use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be passed + to helm template, typically defined as a block + type: string + version: + description: Version is the Helm version to use for + templating ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional + annotations to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional + labels to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies whether + to force applying common annotations to resources + for Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether to + force applying common labels to resources for Kustomize + apps + type: boolean + images: + description: Images is a list of Kustomize image override + specifications + items: + description: KustomizeImage represents a Kustomize + image definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to resources + for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to resources + for Kustomize apps + type: string + version: + description: Version controls which version of Kustomize + to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git repository, + and is only valid for applications sourced from Git. + type: string + plugin: + description: ConfigManagementPlugin holds config management + plugin specific options + properties: + env: + description: Env is a list of environment variable + entries + items: + description: EnvEntry represents an entry in the + application's environment + properties: + name: + description: Name is the name of the variable, + usually expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + type: object + repoURL: + description: RepoURL is the URL to the repository (Git + or Helm) that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of the + source to sync the application to. In case of Git, this + can be commit, tag, or branch. If omitted, will equal + to HEAD. In case of Helm, this is a semver tag for the + Chart's version. + type: string + required: + - repoURL + type: object + required: + - revision + type: object + required: + - operation + - phase + - startedAt + type: object + reconciledAt: + description: ReconciledAt indicates when the application state was + reconciled using the latest git version + format: date-time + type: string + resources: + description: Resources is a list of Kubernetes resources managed by + this application + items: + description: 'ResourceStatus holds the current sync and health status + of a resource TODO: describe members of this type' + properties: + group: + type: string + health: + description: HealthStatus contains information about the currently + observed health state of an application or resource + properties: + message: + description: Message is a human-readable informational message + describing the health status + type: string + status: + description: Status holds the status code of the application + or resource + type: string + type: object + hook: + type: boolean + kind: + type: string + name: + type: string + namespace: + type: string + requiresPruning: + type: boolean + status: + description: SyncStatusCode is a type which represents possible + comparison results + type: string + version: + type: string + type: object + type: array + sourceType: + description: SourceType specifies the type of this application + type: string + summary: + description: Summary contains a list of URLs and container images + used by this application + properties: + externalURLs: + description: ExternalURLs holds all external URLs of application + child resources. + items: + type: string + type: array + images: + description: Images holds all images of application child resources. + items: + type: string + type: array + type: object + sync: + description: Sync contains information about the application's current + sync status + properties: + comparedTo: + description: ComparedTo contains information about what has been + compared + properties: + destination: + description: Destination is a reference to the application's + destination used for comparison + properties: + name: + description: Name is an alternate way of specifying the + target cluster by its symbolic name + type: string + namespace: + description: Namespace specifies the target namespace + for the application's resources. The namespace will + only be set for namespace-scoped resources that have + not set a value for .metadata.namespace + type: string + server: + description: Server specifies the URL of the target cluster + and must be set to the Kubernetes control plane API + type: string + type: object + source: + description: Source is a reference to the application's source + used for comparison + properties: + chart: + description: Chart is a Helm chart name, and must be specified + for applications sourced from a Helm repo. + type: string + directory: + description: Directory holds path/directory specific options + properties: + exclude: + description: Exclude contains a glob pattern to match + paths against that should be explicitly excluded + from being used during manifest generation + type: string + include: + description: Include contains a glob pattern to match + paths against that should be explicitly included + during manifest generation + type: string + jsonnet: + description: Jsonnet holds options specific to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet External + Variables + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level + Arguments + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan a directory + recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters to + the helm template + items: + description: HelmFileParameter is a file parameter + that's passed to helm template during manifest + generation + properties: + name: + description: Name is the name of the Helm parameter + type: string + path: + description: Path is the path to the file containing + the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents helm + template from failing when valueFiles do not exist + locally by not appending them to helm template --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters + which are passed to the helm template command upon + manifest generation + items: + description: HelmParameter is a parameter that's + passed to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether + to tell Helm to interpret booleans and numbers + as strings + type: boolean + name: + description: Name is the name of the Helm parameter + type: string + value: + description: Value is the value for the Helm + parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials to all + domains (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name + to use. If omitted it will use the application name + type: string + skipCrds: + description: SkipCrds skips custom resource definition + installation step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value files + to use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be passed + to helm template, typically defined as a block + type: string + version: + description: Version is the Helm version to use for + templating ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional + annotations to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional + labels to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies whether + to force applying common annotations to resources + for Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether to + force applying common labels to resources for Kustomize + apps + type: boolean + images: + description: Images is a list of Kustomize image override + specifications + items: + description: KustomizeImage represents a Kustomize + image definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to resources + for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to resources + for Kustomize apps + type: string + version: + description: Version controls which version of Kustomize + to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git repository, + and is only valid for applications sourced from Git. + type: string + plugin: + description: ConfigManagementPlugin holds config management + plugin specific options + properties: + env: + description: Env is a list of environment variable + entries + items: + description: EnvEntry represents an entry in the + application's environment + properties: + name: + description: Name is the name of the variable, + usually expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + type: object + repoURL: + description: RepoURL is the URL to the repository (Git + or Helm) that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of the + source to sync the application to. In case of Git, this + can be commit, tag, or branch. If omitted, will equal + to HEAD. In case of Helm, this is a semver tag for the + Chart's version. + type: string + required: + - repoURL + type: object + required: + - destination + - source + type: object + revision: + description: Revision contains information about the revision + the comparison has been performed to + type: string + status: + description: Status is the sync state of the comparison + type: string + required: + - status + type: object + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + labels: + app.kubernetes.io/name: applicationsets.argoproj.io + name: applicationsets.argoproj.io +spec: + group: argoproj.io + names: + kind: ApplicationSet + listKind: ApplicationSetList + plural: applicationsets + shortNames: + - appset + - appsets + singular: applicationset + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + generators: + items: + properties: + clusterDecisionResource: + properties: + configMapRef: + type: string + 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 + name: + type: string + requeueAfterSeconds: + format: int64 + type: integer + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + type: object + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + - source + type: object + required: + - metadata + - spec + type: object + values: + additionalProperties: + type: string + type: object + required: + - configMapRef + type: object + clusters: + properties: + 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 + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + type: object + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + - source + type: object + required: + - metadata + - spec + type: object + values: + additionalProperties: + type: string + type: object + type: object + git: + properties: + directories: + items: + properties: + exclude: + type: boolean + path: + type: string + required: + - path + type: object + type: array + files: + items: + properties: + path: + type: string + required: + - path + type: object + type: array + repoURL: + type: string + requeueAfterSeconds: + format: int64 + type: integer + revision: + type: string + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + type: object + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + - source + type: object + required: + - metadata + - spec + type: object + required: + - repoURL + - revision + type: object + list: + properties: + elements: + items: + x-kubernetes-preserve-unknown-fields: true + type: array + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + type: object + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + - source + type: object + required: + - metadata + - spec + type: object + required: + - elements + type: object + matrix: + properties: + generators: + items: + properties: + clusterDecisionResource: + properties: + configMapRef: + type: string + 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 + name: + type: string + requeueAfterSeconds: + format: int64 + type: integer + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + type: object + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + - source + type: object + required: + - metadata + - spec + type: object + values: + additionalProperties: + type: string + type: object + required: + - configMapRef + type: object + clusters: + properties: + 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 + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + type: object + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + - source + type: object + required: + - metadata + - spec + type: object + values: + additionalProperties: + type: string + type: object + type: object + git: + properties: + directories: + items: + properties: + exclude: + type: boolean + path: + type: string + required: + - path + type: object + type: array + files: + items: + properties: + path: + type: string + required: + - path + type: object + type: array + repoURL: + type: string + requeueAfterSeconds: + format: int64 + type: integer + revision: + type: string + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + type: object + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + - source + type: object + required: + - metadata + - spec + type: object + required: + - repoURL + - revision + type: object + list: + properties: + elements: + items: + x-kubernetes-preserve-unknown-fields: true + type: array + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + type: object + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + - source + type: object + required: + - metadata + - spec + type: object + required: + - elements + type: object + matrix: + x-kubernetes-preserve-unknown-fields: true + merge: + x-kubernetes-preserve-unknown-fields: true + pullRequest: + properties: + bitbucketServer: + properties: + api: + type: string + basicAuth: + properties: + passwordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + username: + type: string + required: + - passwordRef + - username + type: object + project: + type: string + repo: + type: string + required: + - api + - project + - repo + type: object + filters: + items: + properties: + branchMatch: + type: string + type: object + type: array + gitea: + properties: + api: + type: string + insecure: + type: boolean + owner: + type: string + repo: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - api + - owner + - repo + type: object + github: + properties: + api: + type: string + labels: + items: + type: string + type: array + owner: + type: string + repo: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - owner + - repo + type: object + requeueAfterSeconds: + format: int64 + type: integer + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + type: object + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + - source + type: object + required: + - metadata + - spec + type: object + type: object + scmProvider: + properties: + bitbucket: + properties: + allBranches: + type: boolean + appPasswordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + owner: + type: string + user: + type: string + required: + - appPasswordRef + - owner + - user + type: object + bitbucketServer: + properties: + allBranches: + type: boolean + api: + type: string + basicAuth: + properties: + passwordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + username: + type: string + required: + - passwordRef + - username + type: object + project: + type: string + required: + - api + - project + type: object + cloneProtocol: + type: string + filters: + items: + properties: + branchMatch: + type: string + labelMatch: + type: string + pathsDoNotExist: + items: + type: string + type: array + pathsExist: + items: + type: string + type: array + repositoryMatch: + type: string + type: object + type: array + gitea: + properties: + allBranches: + type: boolean + api: + type: string + insecure: + type: boolean + owner: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - api + - owner + type: object + github: + properties: + allBranches: + type: boolean + api: + type: string + organization: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - organization + type: object + gitlab: + properties: + allBranches: + type: boolean + api: + type: string + group: + type: string + includeSubgroups: + type: boolean + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - group + type: object + requeueAfterSeconds: + format: int64 + type: integer + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + type: object + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + - source + type: object + required: + - metadata + - spec + type: object + type: object + type: object + type: array + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + type: object + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + - source + type: object + required: + - metadata + - spec + type: object + required: + - generators + type: object + merge: + properties: + generators: + items: + properties: + clusterDecisionResource: + properties: + configMapRef: + type: string + 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 + name: + type: string + requeueAfterSeconds: + format: int64 + type: integer + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + type: object + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + - source + type: object + required: + - metadata + - spec + type: object + values: + additionalProperties: + type: string + type: object + required: + - configMapRef + type: object + clusters: + properties: + 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 + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + type: object + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + - source + type: object + required: + - metadata + - spec + type: object + values: + additionalProperties: + type: string + type: object + type: object + git: + properties: + directories: + items: + properties: + exclude: + type: boolean + path: + type: string + required: + - path + type: object + type: array + files: + items: + properties: + path: + type: string + required: + - path + type: object + type: array + repoURL: + type: string + requeueAfterSeconds: + format: int64 + type: integer + revision: + type: string + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + type: object + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + - source + type: object + required: + - metadata + - spec + type: object + required: + - repoURL + - revision + type: object + list: + properties: + elements: + items: + x-kubernetes-preserve-unknown-fields: true + type: array + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + type: object + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + - source + type: object + required: + - metadata + - spec + type: object + required: + - elements + type: object + matrix: + x-kubernetes-preserve-unknown-fields: true + merge: + x-kubernetes-preserve-unknown-fields: true + pullRequest: + properties: + bitbucketServer: + properties: + api: + type: string + basicAuth: + properties: + passwordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + username: + type: string + required: + - passwordRef + - username + type: object + project: + type: string + repo: + type: string + required: + - api + - project + - repo + type: object + filters: + items: + properties: + branchMatch: + type: string + type: object + type: array + gitea: + properties: + api: + type: string + insecure: + type: boolean + owner: + type: string + repo: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - api + - owner + - repo + type: object + github: + properties: + api: + type: string + labels: + items: + type: string + type: array + owner: + type: string + repo: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - owner + - repo + type: object + requeueAfterSeconds: + format: int64 + type: integer + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + type: object + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + - source + type: object + required: + - metadata + - spec + type: object + type: object + scmProvider: + properties: + bitbucket: + properties: + allBranches: + type: boolean + appPasswordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + owner: + type: string + user: + type: string + required: + - appPasswordRef + - owner + - user + type: object + bitbucketServer: + properties: + allBranches: + type: boolean + api: + type: string + basicAuth: + properties: + passwordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + username: + type: string + required: + - passwordRef + - username + type: object + project: + type: string + required: + - api + - project + type: object + cloneProtocol: + type: string + filters: + items: + properties: + branchMatch: + type: string + labelMatch: + type: string + pathsDoNotExist: + items: + type: string + type: array + pathsExist: + items: + type: string + type: array + repositoryMatch: + type: string + type: object + type: array + gitea: + properties: + allBranches: + type: boolean + api: + type: string + insecure: + type: boolean + owner: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - api + - owner + type: object + github: + properties: + allBranches: + type: boolean + api: + type: string + organization: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - organization + type: object + gitlab: + properties: + allBranches: + type: boolean + api: + type: string + group: + type: string + includeSubgroups: + type: boolean + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - group + type: object + requeueAfterSeconds: + format: int64 + type: integer + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + type: object + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + - source + type: object + required: + - metadata + - spec + type: object + type: object + type: object + type: array + mergeKeys: + items: + type: string + type: array + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + type: object + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + - source + type: object + required: + - metadata + - spec + type: object + required: + - generators + - mergeKeys + type: object + pullRequest: + properties: + bitbucketServer: + properties: + api: + type: string + basicAuth: + properties: + passwordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + username: + type: string + required: + - passwordRef + - username + type: object + project: + type: string + repo: + type: string + required: + - api + - project + - repo + type: object + filters: + items: + properties: + branchMatch: + type: string + type: object + type: array + gitea: + properties: + api: + type: string + insecure: + type: boolean + owner: + type: string + repo: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - api + - owner + - repo + type: object + github: + properties: + api: + type: string + labels: + items: + type: string + type: array + owner: + type: string + repo: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - owner + - repo + type: object + requeueAfterSeconds: + format: int64 + type: integer + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + type: object + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + - source + type: object + required: + - metadata + - spec + type: object + type: object + scmProvider: + properties: + bitbucket: + properties: + allBranches: + type: boolean + appPasswordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + owner: + type: string + user: + type: string + required: + - appPasswordRef + - owner + - user + type: object + bitbucketServer: + properties: + allBranches: + type: boolean + api: + type: string + basicAuth: + properties: + passwordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + username: + type: string + required: + - passwordRef + - username + type: object + project: + type: string + required: + - api + - project + type: object + cloneProtocol: + type: string + filters: + items: + properties: + branchMatch: + type: string + labelMatch: + type: string + pathsDoNotExist: + items: + type: string + type: array + pathsExist: + items: + type: string + type: array + repositoryMatch: + type: string + type: object + type: array + gitea: + properties: + allBranches: + type: boolean + api: + type: string + insecure: + type: boolean + owner: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - api + - owner + type: object + github: + properties: + allBranches: + type: boolean + api: + type: string + organization: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - organization + type: object + gitlab: + properties: + allBranches: + type: boolean + api: + type: string + group: + type: string + includeSubgroups: + type: boolean + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - group + type: object + requeueAfterSeconds: + format: int64 + type: integer + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + type: object + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + - source + type: object + required: + - metadata + - spec + type: object + type: object + type: object + type: array + syncPolicy: + properties: + preserveResourcesOnDeletion: + type: boolean + type: object + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + type: object + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + - source + type: object + required: + - metadata + - spec + type: object + required: + - generators + - template + type: object + status: + properties: + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + type: string + reason: + type: string + status: + type: string + type: + type: string + required: + - message + - reason + - status + - type + type: object + type: array + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + labels: + app.kubernetes.io/name: appprojects.argoproj.io + app.kubernetes.io/part-of: argocd + name: appprojects.argoproj.io +spec: + group: argoproj.io + names: + kind: AppProject + listKind: AppProjectList + plural: appprojects + shortNames: + - appproj + - appprojs + singular: appproject + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: 'AppProject provides a logical grouping of applications, providing + controls for: * where the apps may deploy to (cluster whitelist) * what + may be deployed (repository whitelist, resource whitelist/blacklist) * who + can access these applications (roles, OIDC group claims bindings) * and + what they can do (RBAC policies) * automation access to these roles (JWT + tokens)' + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: AppProjectSpec is the specification of an AppProject + properties: + clusterResourceBlacklist: + description: ClusterResourceBlacklist contains list of blacklisted + cluster level resources + items: + description: GroupKind specifies a Group and a Kind, but does not + force a version. This is useful for identifying concepts during + lookup stages without having partially valid types + properties: + group: + type: string + kind: + type: string + required: + - group + - kind + type: object + type: array + clusterResourceWhitelist: + description: ClusterResourceWhitelist contains list of whitelisted + cluster level resources + items: + description: GroupKind specifies a Group and a Kind, but does not + force a version. This is useful for identifying concepts during + lookup stages without having partially valid types + properties: + group: + type: string + kind: + type: string + required: + - group + - kind + type: object + type: array + description: + description: Description contains optional project description + type: string + destinations: + description: Destinations contains list of destinations available + for deployment + items: + description: ApplicationDestination holds information about the + application's destination + properties: + name: + description: Name is an alternate way of specifying the target + cluster by its symbolic name + type: string + namespace: + description: Namespace specifies the target namespace for the + application's resources. The namespace will only be set for + namespace-scoped resources that have not set a value for .metadata.namespace + type: string + server: + description: Server specifies the URL of the target cluster + and must be set to the Kubernetes control plane API + type: string + type: object + type: array + namespaceResourceBlacklist: + description: NamespaceResourceBlacklist contains list of blacklisted + namespace level resources + items: + description: GroupKind specifies a Group and a Kind, but does not + force a version. This is useful for identifying concepts during + lookup stages without having partially valid types + properties: + group: + type: string + kind: + type: string + required: + - group + - kind + type: object + type: array + namespaceResourceWhitelist: + description: NamespaceResourceWhitelist contains list of whitelisted + namespace level resources + items: + description: GroupKind specifies a Group and a Kind, but does not + force a version. This is useful for identifying concepts during + lookup stages without having partially valid types + properties: + group: + type: string + kind: + type: string + required: + - group + - kind + type: object + type: array + orphanedResources: + description: OrphanedResources specifies if controller should monitor + orphaned resources of apps in this project + properties: + ignore: + description: Ignore contains a list of resources that are to be + excluded from orphaned resources monitoring + items: + description: OrphanedResourceKey is a reference to a resource + to be ignored from + properties: + group: + type: string + kind: + type: string + name: + type: string + type: object + type: array + warn: + description: Warn indicates if warning condition should be created + for apps which have orphaned resources + type: boolean + type: object + roles: + description: Roles are user defined RBAC roles associated with this + project + items: + description: ProjectRole represents a role that has access to a + project + properties: + description: + description: Description is a description of the role + type: string + groups: + description: Groups are a list of OIDC group claims bound to + this role + items: + type: string + type: array + jwtTokens: + description: JWTTokens are a list of generated JWT tokens bound + to this role + items: + description: JWTToken holds the issuedAt and expiresAt values + of a token + properties: + exp: + format: int64 + type: integer + iat: + format: int64 + type: integer + id: + type: string + required: + - iat + type: object + type: array + name: + description: Name is a name for this role + type: string + policies: + description: Policies Stores a list of casbin formatted strings + that define access policies for the role in the project + items: + type: string + type: array + required: + - name + type: object + type: array + signatureKeys: + description: SignatureKeys contains a list of PGP key IDs that commits + in Git must be signed with in order to be allowed for sync + items: + description: SignatureKey is the specification of a key required + to verify commit signatures with + properties: + keyID: + description: The ID of the key in hexadecimal notation + type: string + required: + - keyID + type: object + type: array + sourceRepos: + description: SourceRepos contains list of repository URLs which can + be used for deployment + items: + type: string + type: array + syncWindows: + description: SyncWindows controls when syncs can be run for apps in + this project + items: + description: SyncWindow contains the kind, time, duration and attributes + that are used to assign the syncWindows to apps + properties: + applications: + description: Applications contains a list of applications that + the window will apply to + items: + type: string + type: array + clusters: + description: Clusters contains a list of clusters that the window + will apply to + items: + type: string + type: array + duration: + description: Duration is the amount of time the sync window + will be open + type: string + kind: + description: Kind defines if the window allows or blocks syncs + type: string + manualSync: + description: ManualSync enables manual syncs when they would + otherwise be blocked + type: boolean + namespaces: + description: Namespaces contains a list of namespaces that the + window will apply to + items: + type: string + type: array + schedule: + description: Schedule is the time the window will begin, specified + in cron format + type: string + timeZone: + description: TimeZone of the sync that will be applied to the + schedule + type: string + type: object + type: array + type: object + status: + description: AppProjectStatus contains status information for AppProject + CRs + properties: + jwtTokensByRole: + additionalProperties: + description: JWTTokens represents a list of JWT tokens + properties: + items: + items: + description: JWTToken holds the issuedAt and expiresAt values + of a token + properties: + exp: + format: int64 + type: integer + iat: + format: int64 + type: integer + id: + type: string + required: + - iat + type: object + type: array + type: object + description: JWTTokensByRole contains a list of JWT tokens issued + for a given role + type: object + type: object + required: + - metadata + - spec + type: object + served: true + storage: true +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + app.kubernetes.io/component: application-controller + app.kubernetes.io/name: argocd-application-controller + app.kubernetes.io/part-of: argocd + name: argocd-application-controller +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/name: argocd-applicationset-controller + app.kubernetes.io/part-of: argocd-applicationset + name: argocd-applicationset-controller +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + app.kubernetes.io/component: dex-server + app.kubernetes.io/name: argocd-dex-server + app.kubernetes.io/part-of: argocd + name: argocd-dex-server +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: argocd-notifications-controller +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + app.kubernetes.io/component: redis + app.kubernetes.io/name: argocd-redis + app.kubernetes.io/part-of: argocd + name: argocd-redis +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + app.kubernetes.io/component: repo-server + app.kubernetes.io/name: argocd-repo-server + app.kubernetes.io/part-of: argocd + name: argocd-repo-server +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + app.kubernetes.io/component: server + app.kubernetes.io/name: argocd-server + app.kubernetes.io/part-of: argocd + name: argocd-server +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + labels: + app.kubernetes.io/component: application-controller + app.kubernetes.io/name: argocd-application-controller + app.kubernetes.io/part-of: argocd + name: argocd-application-controller +rules: +- apiGroups: + - "" + resources: + - secrets + - configmaps + verbs: + - get + - list + - watch +- apiGroups: + - argoproj.io + resources: + - applications + - appprojects + verbs: + - create + - get + - list + - watch + - update + - patch + - delete +- apiGroups: + - "" + resources: + - events + verbs: + - create + - list +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/name: argocd-applicationset-controller + app.kubernetes.io/part-of: argocd-applicationset + name: argocd-applicationset-controller +rules: +- apiGroups: + - argoproj.io + resources: + - applications + - applicationsets + - applicationsets/finalizers + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - argoproj.io + resources: + - appprojects + verbs: + - get +- apiGroups: + - argoproj.io + resources: + - applicationsets/status + verbs: + - get + - patch + - update +- apiGroups: + - "" + resources: + - events + verbs: + - create + - get + - list + - patch + - watch +- apiGroups: + - "" + resources: + - secrets + - configmaps + verbs: + - get + - list + - watch +- apiGroups: + - apps + - extensions + resources: + - deployments + verbs: + - get + - list + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + labels: + app.kubernetes.io/component: dex-server + app.kubernetes.io/name: argocd-dex-server + app.kubernetes.io/part-of: argocd + name: argocd-dex-server +rules: +- apiGroups: + - "" + resources: + - secrets + - configmaps + verbs: + - get + - list + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: argocd-notifications-controller +rules: +- apiGroups: + - argoproj.io + resources: + - applications + - appprojects + verbs: + - get + - list + - watch + - update + - patch +- apiGroups: + - "" + resources: + - configmaps + - secrets + verbs: + - list + - watch +- apiGroups: + - "" + resourceNames: + - argocd-notifications-cm + resources: + - configmaps + verbs: + - get +- apiGroups: + - "" + resourceNames: + - argocd-notifications-secret + resources: + - secrets + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + labels: + app.kubernetes.io/component: server + app.kubernetes.io/name: argocd-server + app.kubernetes.io/part-of: argocd + name: argocd-server +rules: +- apiGroups: + - "" + resources: + - secrets + - configmaps + verbs: + - create + - get + - list + - watch + - update + - patch + - delete +- apiGroups: + - argoproj.io + resources: + - applications + - appprojects + verbs: + - create + - get + - list + - watch + - update + - delete + - patch +- apiGroups: + - "" + resources: + - events + verbs: + - create + - list +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/component: application-controller + app.kubernetes.io/name: argocd-application-controller + app.kubernetes.io/part-of: argocd + name: argocd-application-controller +rules: +- apiGroups: + - '*' + resources: + - '*' + verbs: + - '*' +- nonResourceURLs: + - '*' + verbs: + - '*' +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/component: server + app.kubernetes.io/name: argocd-server + app.kubernetes.io/part-of: argocd + name: argocd-server +rules: +- apiGroups: + - '*' + resources: + - '*' + verbs: + - delete + - get + - patch +- apiGroups: + - "" + resources: + - events + verbs: + - list +- apiGroups: + - "" + resources: + - pods + - pods/log + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + labels: + app.kubernetes.io/component: application-controller + app.kubernetes.io/name: argocd-application-controller + app.kubernetes.io/part-of: argocd + name: argocd-application-controller +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: argocd-application-controller +subjects: +- kind: ServiceAccount + name: argocd-application-controller +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/name: argocd-applicationset-controller + app.kubernetes.io/part-of: argocd-applicationset + name: argocd-applicationset-controller +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: argocd-applicationset-controller +subjects: +- kind: ServiceAccount + name: argocd-applicationset-controller +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + labels: + app.kubernetes.io/component: dex-server + app.kubernetes.io/name: argocd-dex-server + app.kubernetes.io/part-of: argocd + name: argocd-dex-server +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: argocd-dex-server +subjects: +- kind: ServiceAccount + name: argocd-dex-server +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: argocd-notifications-controller +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: argocd-notifications-controller +subjects: +- kind: ServiceAccount + name: argocd-notifications-controller +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + labels: + app.kubernetes.io/component: redis + app.kubernetes.io/name: argocd-redis + app.kubernetes.io/part-of: argocd + name: argocd-redis +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: argocd-redis +subjects: +- kind: ServiceAccount + name: argocd-redis +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + labels: + app.kubernetes.io/component: server + app.kubernetes.io/name: argocd-server + app.kubernetes.io/part-of: argocd + name: argocd-server +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: argocd-server +subjects: +- kind: ServiceAccount + name: argocd-server +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + app.kubernetes.io/component: application-controller + app.kubernetes.io/name: argocd-application-controller + app.kubernetes.io/part-of: argocd + name: argocd-application-controller +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: argocd-application-controller +subjects: +- kind: ServiceAccount + name: argocd-application-controller + namespace: argocd +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + app.kubernetes.io/component: server + app.kubernetes.io/name: argocd-server + app.kubernetes.io/part-of: argocd + name: argocd-server +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: argocd-server +subjects: +- kind: ServiceAccount + name: argocd-server + namespace: argocd +--- +apiVersion: v1 +kind: ConfigMap +metadata: + labels: + app.kubernetes.io/name: argocd-cm + app.kubernetes.io/part-of: argocd + name: argocd-cm +--- +apiVersion: v1 +kind: ConfigMap +metadata: + labels: + app.kubernetes.io/name: argocd-cmd-params-cm + app.kubernetes.io/part-of: argocd + name: argocd-cmd-params-cm +data: + server.insecure: "true" +--- +apiVersion: v1 +kind: ConfigMap +metadata: + labels: + app.kubernetes.io/name: argocd-gpg-keys-cm + app.kubernetes.io/part-of: argocd + name: argocd-gpg-keys-cm +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: argocd-notifications-cm +--- +apiVersion: v1 +kind: ConfigMap +metadata: + labels: + app.kubernetes.io/name: argocd-rbac-cm + app.kubernetes.io/part-of: argocd + name: argocd-rbac-cm +--- +apiVersion: v1 +data: + ssh_known_hosts: |- + bitbucket.org ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAubiN81eDcafrgMeLzaFPsw2kNvEcqTKl/VqLat/MaB33pZy0y3rJZtnqwR2qOOvbwKZYKiEO1O6VqNEBxKvJJelCq0dTXWT5pbO2gDXC6h6QDXCaHo6pOHGPUy+YBaGQRGuSusMEASYiWunYN0vCAI8QaXnWMXNMdFP3jHAJH0eDsoiGnLPBlBp4TNm6rYI74nMzgz3B9IikW4WVK+dc8KZJZWYjAuORU3jc1c/NPskD2ASinf8v3xnfXeukU0sJ5N6m5E8VLjObPEO+mN2t/FZTMZLiFqPWc/ALSqnMnnhwrNi2rbfg/rd/IpL8Le3pSBne8+seeFVBoGqzHM9yXw== + github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ== + gitlab.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFSMqzJeV9rUzU4kWitGjeR4PWSa29SPqJ1fVkhtj3Hw9xjLVXVYrU9QlYWrOLXBpQ6KWjbjTDTdDkoohFzgbEY= + gitlab.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAfuCHKVTjquxvt6CM6tdG4SLp1Btn/nOeHHE5UOzRdf + gitlab.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCsj2bNKTBSpIYDEGk9KxsGh3mySTRgMtXL583qmBpzeQ+jqCMRgBqB98u3z++J1sKlXHWfM9dyhSevkMwSbhoR8XIq/U0tCNyokEi/ueaBMCvbcTHhO7FcwzY92WK4Yt0aGROY5qX2UKSeOvuP4D6TPqKF1onrSzH9bx9XUf2lEdWT/ia1NEKjunUqu1xOB/StKDHMoX4/OKyIzuS0q/T1zOATthvasJFoPrAjkohTyaDUz2LN5JoH839hViyEG82yB+MjcFV5MU3N1l1QL3cVUCh93xSaua1N85qivl+siMkPGbO5xR/En4iEY6K2XPASUEMaieWVNTRCtJ4S8H+9 + ssh.dev.azure.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7Hr1oTWqNqOlzGJOfGJ4NakVyIzf1rXYd4d7wo6jBlkLvCA4odBlL0mDUyZ0/QUfTTqeu+tm22gOsv+VrVTMk6vwRU75gY/y9ut5Mb3bR5BV58dKXyq9A9UeB5Cakehn5Zgm6x1mKoVyf+FFn26iYqXJRgzIZZcZ5V6hrE0Qg39kZm4az48o0AUbf6Sp4SLdvnuMa2sVNwHBboS7EJkm57XQPVU3/QpyNLHbWDdzwtrlS+ez30S3AdYhLKEOxAG8weOnyrtLJAUen9mTkol8oII1edf7mWWbWVf0nBmly21+nZcmCTISQBtdcyPaEno7fFQMDD26/s0lfKob4Kw8H + vs-ssh.visualstudio.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7Hr1oTWqNqOlzGJOfGJ4NakVyIzf1rXYd4d7wo6jBlkLvCA4odBlL0mDUyZ0/QUfTTqeu+tm22gOsv+VrVTMk6vwRU75gY/y9ut5Mb3bR5BV58dKXyq9A9UeB5Cakehn5Zgm6x1mKoVyf+FFn26iYqXJRgzIZZcZ5V6hrE0Qg39kZm4az48o0AUbf6Sp4SLdvnuMa2sVNwHBboS7EJkm57XQPVU3/QpyNLHbWDdzwtrlS+ez30S3AdYhLKEOxAG8weOnyrtLJAUen9mTkol8oII1edf7mWWbWVf0nBmly21+nZcmCTISQBtdcyPaEno7fFQMDD26/s0lfKob4Kw8H + github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg= + github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl +kind: ConfigMap +metadata: + labels: + app.kubernetes.io/name: argocd-ssh-known-hosts-cm + app.kubernetes.io/part-of: argocd + name: argocd-ssh-known-hosts-cm +--- +apiVersion: v1 +kind: ConfigMap +metadata: + labels: + app.kubernetes.io/name: argocd-tls-certs-cm + app.kubernetes.io/part-of: argocd + name: argocd-tls-certs-cm +--- +apiVersion: v1 +kind: Secret +metadata: + name: argocd-notifications-secret +type: Opaque +--- +apiVersion: v1 +kind: Secret +metadata: + labels: + app.kubernetes.io/name: argocd-secret + app.kubernetes.io/part-of: argocd + name: argocd-secret +type: Opaque +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/name: argocd-applicationset-controller + app.kubernetes.io/part-of: argocd-applicationset + name: argocd-applicationset-controller +spec: + ports: + - name: webhook + port: 7000 + protocol: TCP + targetPort: webhook + - name: metrics + port: 8080 + protocol: TCP + targetPort: metrics + selector: + app.kubernetes.io/name: argocd-applicationset-controller +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/component: dex-server + app.kubernetes.io/name: argocd-dex-server + app.kubernetes.io/part-of: argocd + name: argocd-dex-server +spec: + ports: + - name: http + port: 5556 + protocol: TCP + targetPort: 5556 + - name: grpc + port: 5557 + protocol: TCP + targetPort: 5557 + - name: metrics + port: 5558 + protocol: TCP + targetPort: 5558 + selector: + app.kubernetes.io/name: argocd-dex-server +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/component: metrics + app.kubernetes.io/name: argocd-metrics + app.kubernetes.io/part-of: argocd + name: argocd-metrics +spec: + ports: + - name: metrics + port: 8082 + protocol: TCP + targetPort: 8082 + selector: + app.kubernetes.io/name: argocd-application-controller +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/name: argocd-notifications-controller-metrics + name: argocd-notifications-controller-metrics +spec: + ports: + - name: metrics + port: 9001 + protocol: TCP + targetPort: 9001 + selector: + app.kubernetes.io/name: argocd-notifications-controller +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/component: redis + app.kubernetes.io/name: argocd-redis + app.kubernetes.io/part-of: argocd + name: argocd-redis +spec: + ports: + - name: tcp-redis + port: 6379 + targetPort: 6379 + selector: + app.kubernetes.io/name: argocd-redis +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/component: repo-server + app.kubernetes.io/name: argocd-repo-server + app.kubernetes.io/part-of: argocd + name: argocd-repo-server +spec: + ports: + - name: server + port: 8081 + protocol: TCP + targetPort: 8081 + - name: metrics + port: 8084 + protocol: TCP + targetPort: 8084 + selector: + app.kubernetes.io/name: argocd-repo-server +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/component: server + app.kubernetes.io/name: argocd-server + app.kubernetes.io/part-of: argocd + name: argocd-server +spec: + type: ClusterIP + ports: + - name: http + port: 80 + protocol: TCP + targetPort: 8080 + - name: https + port: 443 + protocol: TCP + targetPort: 8080 + selector: + app.kubernetes.io/name: argocd-server +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/component: server + app.kubernetes.io/name: argocd-server-metrics + app.kubernetes.io/part-of: argocd + name: argocd-server-metrics +spec: + ports: + - name: metrics + port: 8083 + protocol: TCP + targetPort: 8083 + selector: + app.kubernetes.io/name: argocd-server +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/name: argocd-applicationset-controller + app.kubernetes.io/part-of: argocd-applicationset + name: argocd-applicationset-controller +spec: + selector: + matchLabels: + app.kubernetes.io/name: argocd-applicationset-controller + template: + metadata: + labels: + app.kubernetes.io/name: argocd-applicationset-controller + spec: + containers: + - command: + - entrypoint.sh + - argocd-applicationset-controller + env: + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + image: quay.io/argoproj/argocd:v2.4.9 + imagePullPolicy: Always + name: argocd-applicationset-controller + ports: + - containerPort: 7000 + name: webhook + - containerPort: 8080 + name: metrics + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + volumeMounts: + - mountPath: /app/config/ssh + name: ssh-known-hosts + - mountPath: /app/config/tls + name: tls-certs + - mountPath: /app/config/gpg/source + name: gpg-keys + - mountPath: /app/config/gpg/keys + name: gpg-keyring + - mountPath: /tmp + name: tmp + serviceAccountName: argocd-applicationset-controller + volumes: + - configMap: + name: argocd-ssh-known-hosts-cm + name: ssh-known-hosts + - configMap: + name: argocd-tls-certs-cm + name: tls-certs + - configMap: + name: argocd-gpg-keys-cm + name: gpg-keys + - emptyDir: {} + name: gpg-keyring + - emptyDir: {} + name: tmp +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app.kubernetes.io/component: dex-server + app.kubernetes.io/name: argocd-dex-server + app.kubernetes.io/part-of: argocd + name: argocd-dex-server +spec: + selector: + matchLabels: + app.kubernetes.io/name: argocd-dex-server + template: + metadata: + labels: + app.kubernetes.io/name: argocd-dex-server + spec: + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchLabels: + app.kubernetes.io/part-of: argocd + topologyKey: kubernetes.io/hostname + weight: 5 + containers: + - command: + - /shared/argocd-dex + - rundex + image: ghcr.io/dexidp/dex:v2.32.0 + imagePullPolicy: Always + name: dex + ports: + - containerPort: 5556 + - containerPort: 5557 + - containerPort: 5558 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + volumeMounts: + - mountPath: /shared + name: static-files + - mountPath: /tmp + name: dexconfig + initContainers: + - command: + - cp + - -n + - /usr/local/bin/argocd + - /shared/argocd-dex + image: quay.io/argoproj/argocd:v2.4.9 + imagePullPolicy: Always + name: copyutil + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + volumeMounts: + - mountPath: /shared + name: static-files + - mountPath: /tmp + name: dexconfig + serviceAccountName: argocd-dex-server + volumes: + - emptyDir: {} + name: static-files + - emptyDir: {} + name: dexconfig +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: argocd-notifications-controller +spec: + selector: + matchLabels: + app.kubernetes.io/name: argocd-notifications-controller + strategy: + type: Recreate + template: + metadata: + labels: + app.kubernetes.io/name: argocd-notifications-controller + spec: + containers: + - command: + - argocd-notifications + image: quay.io/argoproj/argocd:v2.4.9 + imagePullPolicy: Always + livenessProbe: + tcpSocket: + port: 9001 + name: argocd-notifications-controller + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + volumeMounts: + - mountPath: /app/config/tls + name: tls-certs + - mountPath: /app/config/reposerver/tls + name: argocd-repo-server-tls + workingDir: /app + securityContext: + runAsNonRoot: true + serviceAccountName: argocd-notifications-controller + volumes: + - configMap: + name: argocd-tls-certs-cm + name: tls-certs + - name: argocd-repo-server-tls + secret: + items: + - key: tls.crt + path: tls.crt + - key: tls.key + path: tls.key + - key: ca.crt + path: ca.crt + optional: true + secretName: argocd-repo-server-tls +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app.kubernetes.io/component: redis + app.kubernetes.io/name: argocd-redis + app.kubernetes.io/part-of: argocd + name: argocd-redis +spec: + selector: + matchLabels: + app.kubernetes.io/name: argocd-redis + template: + metadata: + labels: + app.kubernetes.io/name: argocd-redis + spec: + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchLabels: + app.kubernetes.io/name: argocd-redis + topologyKey: kubernetes.io/hostname + weight: 100 + - podAffinityTerm: + labelSelector: + matchLabels: + app.kubernetes.io/part-of: argocd + topologyKey: kubernetes.io/hostname + weight: 5 + containers: + - args: + - --save + - "" + - --appendonly + - "no" + image: redis:7.0.4-alpine + imagePullPolicy: Always + name: redis + ports: + - containerPort: 6379 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + securityContext: + runAsNonRoot: true + runAsUser: 999 + serviceAccountName: argocd-redis +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app.kubernetes.io/component: repo-server + app.kubernetes.io/name: argocd-repo-server + app.kubernetes.io/part-of: argocd + name: argocd-repo-server +spec: + selector: + matchLabels: + app.kubernetes.io/name: argocd-repo-server + template: + metadata: + labels: + app.kubernetes.io/name: argocd-repo-server + spec: + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchLabels: + app.kubernetes.io/name: argocd-repo-server + topologyKey: kubernetes.io/hostname + weight: 100 + - podAffinityTerm: + labelSelector: + matchLabels: + app.kubernetes.io/part-of: argocd + topologyKey: kubernetes.io/hostname + weight: 5 + automountServiceAccountToken: false + containers: + - command: + - sh + - -c + - entrypoint.sh argocd-repo-server --redis argocd-redis:6379 + env: + - name: ARGOCD_RECONCILIATION_TIMEOUT + valueFrom: + configMapKeyRef: + key: timeout.reconciliation + name: argocd-cm + optional: true + - name: ARGOCD_REPO_SERVER_LOGFORMAT + valueFrom: + configMapKeyRef: + key: reposerver.log.format + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_REPO_SERVER_LOGLEVEL + valueFrom: + configMapKeyRef: + key: reposerver.log.level + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_REPO_SERVER_PARALLELISM_LIMIT + valueFrom: + configMapKeyRef: + key: reposerver.parallelism.limit + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_REPO_SERVER_DISABLE_TLS + valueFrom: + configMapKeyRef: + key: reposerver.disable.tls + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_TLS_MIN_VERSION + valueFrom: + configMapKeyRef: + key: reposerver.tls.minversion + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_TLS_MAX_VERSION + valueFrom: + configMapKeyRef: + key: reposerver.tls.maxversion + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_TLS_CIPHERS + valueFrom: + configMapKeyRef: + key: reposerver.tls.ciphers + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_REPO_CACHE_EXPIRATION + valueFrom: + configMapKeyRef: + key: reposerver.repo.cache.expiration + name: argocd-cmd-params-cm + optional: true + - name: REDIS_SERVER + valueFrom: + configMapKeyRef: + key: redis.server + name: argocd-cmd-params-cm + optional: true + - name: REDISDB + valueFrom: + configMapKeyRef: + key: redis.db + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_DEFAULT_CACHE_EXPIRATION + valueFrom: + configMapKeyRef: + key: reposerver.default.cache.expiration + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_REPO_SERVER_OTLP_ADDRESS + valueFrom: + configMapKeyRef: + key: otlp.address + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_REPO_SERVER_MAX_COMBINED_DIRECTORY_MANIFESTS_SIZE + valueFrom: + configMapKeyRef: + key: reposerver.max.combined.directory.manifests.size + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_REPO_SERVER_PLUGIN_TAR_EXCLUSIONS + valueFrom: + configMapKeyRef: + key: reposerver.plugin.tar.exclusions + name: argocd-cmd-params-cm + optional: true + - name: HELM_CACHE_HOME + value: /helm-working-dir + - name: HELM_CONFIG_HOME + value: /helm-working-dir + - name: HELM_DATA_HOME + value: /helm-working-dir + image: quay.io/argoproj/argocd:v2.4.9 + imagePullPolicy: Always + livenessProbe: + failureThreshold: 3 + httpGet: + path: /healthz?full=true + port: 8084 + initialDelaySeconds: 30 + periodSeconds: 5 + name: argocd-repo-server + ports: + - containerPort: 8081 + - containerPort: 8084 + readinessProbe: + httpGet: + path: /healthz + port: 8084 + initialDelaySeconds: 5 + periodSeconds: 10 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + volumeMounts: + - mountPath: /app/config/ssh + name: ssh-known-hosts + - mountPath: /app/config/tls + name: tls-certs + - mountPath: /app/config/gpg/source + name: gpg-keys + - mountPath: /app/config/gpg/keys + name: gpg-keyring + - mountPath: /app/config/reposerver/tls + name: argocd-repo-server-tls + - mountPath: /tmp + name: tmp + - mountPath: /helm-working-dir + name: helm-working-dir + - mountPath: /home/argocd/cmp-server/plugins + name: plugins + initContainers: + - command: + - cp + - -n + - /usr/local/bin/argocd + - /var/run/argocd/argocd-cmp-server + image: quay.io/argoproj/argocd:v2.4.9 + name: copyutil + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + volumeMounts: + - mountPath: /var/run/argocd + name: var-files + serviceAccountName: argocd-repo-server + volumes: + - configMap: + name: argocd-ssh-known-hosts-cm + name: ssh-known-hosts + - configMap: + name: argocd-tls-certs-cm + name: tls-certs + - configMap: + name: argocd-gpg-keys-cm + name: gpg-keys + - emptyDir: {} + name: gpg-keyring + - emptyDir: {} + name: tmp + - emptyDir: {} + name: helm-working-dir + - name: argocd-repo-server-tls + secret: + items: + - key: tls.crt + path: tls.crt + - key: tls.key + path: tls.key + - key: ca.crt + path: ca.crt + optional: true + secretName: argocd-repo-server-tls + - emptyDir: {} + name: var-files + - emptyDir: {} + name: plugins +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app.kubernetes.io/component: server + app.kubernetes.io/name: argocd-server + app.kubernetes.io/part-of: argocd + name: argocd-server +spec: + selector: + matchLabels: + app.kubernetes.io/name: argocd-server + template: + metadata: + labels: + app.kubernetes.io/name: argocd-server + spec: + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchLabels: + app.kubernetes.io/name: argocd-server + topologyKey: kubernetes.io/hostname + weight: 100 + - podAffinityTerm: + labelSelector: + matchLabels: + app.kubernetes.io/part-of: argocd + topologyKey: kubernetes.io/hostname + weight: 5 + containers: + - command: + - argocd-server + env: + - name: ARGOCD_SERVER_INSECURE + valueFrom: + configMapKeyRef: + key: server.insecure + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_SERVER_BASEHREF + valueFrom: + configMapKeyRef: + key: server.basehref + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_SERVER_ROOTPATH + valueFrom: + configMapKeyRef: + key: server.rootpath + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_SERVER_LOGFORMAT + valueFrom: + configMapKeyRef: + key: server.log.format + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_REPO_SERVER_LOGLEVEL + valueFrom: + configMapKeyRef: + key: server.log.level + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_SERVER_REPO_SERVER + valueFrom: + configMapKeyRef: + key: repo.server + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_SERVER_DEX_SERVER + valueFrom: + configMapKeyRef: + key: server.dex.server + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_SERVER_DISABLE_AUTH + valueFrom: + configMapKeyRef: + key: server.disable.auth + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_SERVER_ENABLE_GZIP + valueFrom: + configMapKeyRef: + key: server.enable.gzip + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_SERVER_REPO_SERVER_TIMEOUT_SECONDS + valueFrom: + configMapKeyRef: + key: server.repo.server.timeout.seconds + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_SERVER_X_FRAME_OPTIONS + valueFrom: + configMapKeyRef: + key: server.x.frame.options + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_SERVER_CONTENT_SECURITY_POLICY + valueFrom: + configMapKeyRef: + key: server.content.security.policy + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_SERVER_REPO_SERVER_PLAINTEXT + valueFrom: + configMapKeyRef: + key: server.repo.server.plaintext + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_SERVER_REPO_SERVER_STRICT_TLS + valueFrom: + configMapKeyRef: + key: server.repo.server.strict.tls + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_TLS_MIN_VERSION + valueFrom: + configMapKeyRef: + key: server.tls.minversion + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_TLS_MAX_VERSION + valueFrom: + configMapKeyRef: + key: server.tls.maxversion + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_TLS_CIPHERS + valueFrom: + configMapKeyRef: + key: server.tls.ciphers + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_SERVER_CONNECTION_STATUS_CACHE_EXPIRATION + valueFrom: + configMapKeyRef: + key: server.connection.status.cache.expiration + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_SERVER_OIDC_CACHE_EXPIRATION + valueFrom: + configMapKeyRef: + key: server.oidc.cache.expiration + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_SERVER_LOGIN_ATTEMPTS_EXPIRATION + valueFrom: + configMapKeyRef: + key: server.login.attempts.expiration + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_SERVER_STATIC_ASSETS + valueFrom: + configMapKeyRef: + key: server.staticassets + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_APP_STATE_CACHE_EXPIRATION + valueFrom: + configMapKeyRef: + key: server.app.state.cache.expiration + name: argocd-cmd-params-cm + optional: true + - name: REDIS_SERVER + valueFrom: + configMapKeyRef: + key: redis.server + name: argocd-cmd-params-cm + optional: true + - name: REDISDB + valueFrom: + configMapKeyRef: + key: redis.db + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_DEFAULT_CACHE_EXPIRATION + valueFrom: + configMapKeyRef: + key: server.default.cache.expiration + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_MAX_COOKIE_NUMBER + valueFrom: + configMapKeyRef: + key: server.http.cookie.maxnumber + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_SERVER_OTLP_ADDRESS + valueFrom: + configMapKeyRef: + key: otlp.address + name: argocd-cmd-params-cm + optional: true + image: quay.io/argoproj/argocd:v2.4.9 + imagePullPolicy: Always + livenessProbe: + httpGet: + path: /healthz?full=true + port: 8080 + initialDelaySeconds: 3 + periodSeconds: 30 + name: argocd-server + ports: + - containerPort: 8080 + - containerPort: 8083 + readinessProbe: + httpGet: + path: /healthz + port: 8080 + initialDelaySeconds: 3 + periodSeconds: 30 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + volumeMounts: + - mountPath: /app/config/ssh + name: ssh-known-hosts + - mountPath: /app/config/tls + name: tls-certs + - mountPath: /app/config/server/tls + name: argocd-repo-server-tls + - mountPath: /home/argocd + name: plugins-home + - mountPath: /tmp + name: tmp + serviceAccountName: argocd-server + volumes: + - emptyDir: {} + name: plugins-home + - emptyDir: {} + name: tmp + - configMap: + name: argocd-ssh-known-hosts-cm + name: ssh-known-hosts + - configMap: + name: argocd-tls-certs-cm + name: tls-certs + - name: argocd-repo-server-tls + secret: + items: + - key: tls.crt + path: tls.crt + - key: tls.key + path: tls.key + - key: ca.crt + path: ca.crt + optional: true + secretName: argocd-repo-server-tls +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + labels: + app.kubernetes.io/component: application-controller + app.kubernetes.io/name: argocd-application-controller + app.kubernetes.io/part-of: argocd + name: argocd-application-controller +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: argocd-application-controller + serviceName: argocd-application-controller + template: + metadata: + labels: + app.kubernetes.io/name: argocd-application-controller + spec: + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchLabels: + app.kubernetes.io/name: argocd-application-controller + topologyKey: kubernetes.io/hostname + weight: 100 + - podAffinityTerm: + labelSelector: + matchLabels: + app.kubernetes.io/part-of: argocd + topologyKey: kubernetes.io/hostname + weight: 5 + containers: + - command: + - argocd-application-controller + env: + - name: ARGOCD_CONTROLLER_REPLICAS + value: "1" + - name: ARGOCD_RECONCILIATION_TIMEOUT + valueFrom: + configMapKeyRef: + key: timeout.reconciliation + name: argocd-cm + optional: true + - name: ARGOCD_HARD_RECONCILIATION_TIMEOUT + valueFrom: + configMapKeyRef: + key: timeout.hard.reconciliation + name: argocd-cm + optional: true + - name: ARGOCD_APPLICATION_CONTROLLER_REPO_SERVER + valueFrom: + configMapKeyRef: + key: repo.server + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_APPLICATION_CONTROLLER_REPO_SERVER_TIMEOUT_SECONDS + valueFrom: + configMapKeyRef: + key: controller.repo.server.timeout.seconds + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_APPLICATION_CONTROLLER_STATUS_PROCESSORS + valueFrom: + configMapKeyRef: + key: controller.status.processors + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_APPLICATION_CONTROLLER_OPERATION_PROCESSORS + valueFrom: + configMapKeyRef: + key: controller.operation.processors + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_APPLICATION_CONTROLLER_LOGFORMAT + valueFrom: + configMapKeyRef: + key: controller.log.format + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_APPLICATION_CONTROLLER_LOGLEVEL + valueFrom: + configMapKeyRef: + key: controller.log.level + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_APPLICATION_CONTROLLER_METRICS_CACHE_EXPIRATION + valueFrom: + configMapKeyRef: + key: controller.metrics.cache.expiration + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_TIMEOUT_SECONDS + valueFrom: + configMapKeyRef: + key: controller.self.heal.timeout.seconds + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_APPLICATION_CONTROLLER_REPO_SERVER_PLAINTEXT + valueFrom: + configMapKeyRef: + key: controller.repo.server.plaintext + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_APPLICATION_CONTROLLER_REPO_SERVER_STRICT_TLS + valueFrom: + configMapKeyRef: + key: controller.repo.server.strict.tls + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_APP_STATE_CACHE_EXPIRATION + valueFrom: + configMapKeyRef: + key: controller.app.state.cache.expiration + name: argocd-cmd-params-cm + optional: true + - name: REDIS_SERVER + valueFrom: + configMapKeyRef: + key: redis.server + name: argocd-cmd-params-cm + optional: true + - name: REDISDB + valueFrom: + configMapKeyRef: + key: redis.db + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_DEFAULT_CACHE_EXPIRATION + valueFrom: + configMapKeyRef: + key: controller.default.cache.expiration + name: argocd-cmd-params-cm + optional: true + - name: ARGOCD_APPLICATION_CONTROLLER_OTLP_ADDRESS + valueFrom: + configMapKeyRef: + key: otlp.address + name: argocd-cmd-params-cm + optional: true + image: quay.io/argoproj/argocd:v2.4.9 + imagePullPolicy: Always + livenessProbe: + httpGet: + path: /healthz + port: 8082 + initialDelaySeconds: 5 + periodSeconds: 10 + name: argocd-application-controller + ports: + - containerPort: 8082 + readinessProbe: + httpGet: + path: /healthz + port: 8082 + initialDelaySeconds: 5 + periodSeconds: 10 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + volumeMounts: + - mountPath: /app/config/controller/tls + name: argocd-repo-server-tls + - mountPath: /home/argocd + name: argocd-home + workingDir: /home/argocd + serviceAccountName: argocd-application-controller + volumes: + - emptyDir: {} + name: argocd-home + - name: argocd-repo-server-tls + secret: + items: + - key: tls.crt + path: tls.crt + - key: tls.key + path: tls.key + - key: ca.crt + path: ca.crt + optional: true + secretName: argocd-repo-server-tls +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: argocd-application-controller-network-policy +spec: + ingress: + - from: + - namespaceSelector: {} + ports: + - port: 8082 + podSelector: + matchLabels: + app.kubernetes.io/name: argocd-application-controller + policyTypes: + - Ingress +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: argocd-dex-server-network-policy +spec: + ingress: + - from: + - podSelector: + matchLabels: + app.kubernetes.io/name: argocd-server + ports: + - port: 5556 + protocol: TCP + - port: 5557 + protocol: TCP + - from: + - namespaceSelector: {} + ports: + - port: 5558 + protocol: TCP + podSelector: + matchLabels: + app.kubernetes.io/name: argocd-dex-server + policyTypes: + - Ingress +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: argocd-redis-network-policy +spec: + ingress: + - from: + - podSelector: + matchLabels: + app.kubernetes.io/name: argocd-server + - podSelector: + matchLabels: + app.kubernetes.io/name: argocd-repo-server + - podSelector: + matchLabels: + app.kubernetes.io/name: argocd-application-controller + ports: + - port: 6379 + protocol: TCP + podSelector: + matchLabels: + app.kubernetes.io/name: argocd-redis + policyTypes: + - Ingress +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: argocd-repo-server-network-policy +spec: + ingress: + - from: + - podSelector: + matchLabels: + app.kubernetes.io/name: argocd-server + - podSelector: + matchLabels: + app.kubernetes.io/name: argocd-application-controller + - podSelector: + matchLabels: + app.kubernetes.io/name: argocd-notifications-controller + ports: + - port: 8081 + protocol: TCP + - from: + - namespaceSelector: {} + ports: + - port: 8084 + podSelector: + matchLabels: + app.kubernetes.io/name: argocd-repo-server + policyTypes: + - Ingress +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: argocd-server-network-policy +spec: + ingress: + - {} + podSelector: + matchLabels: + app.kubernetes.io/name: argocd-server + policyTypes: + - Ingress diff --git a/ResearchOps/cert-manager/ClusterIssuer.yaml b/ResearchOps/cert-manager/ClusterIssuer.yaml new file mode 100644 index 0000000..ff43de7 --- /dev/null +++ b/ResearchOps/cert-manager/ClusterIssuer.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: cloudflare-api-token-secret + namespace: cert-manager +type: Opaque +stringData: + api-token: sZtljE0iuaNy1pb1veCv3jln_B85cRkZ8SPOROe_ +--- +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: letsencrypt-dns01 +spec: + acme: + server: https://acme-v02.api.letsencrypt.org/directory + privateKeySecretRef: + name: letsencrypt-dns01 + solvers: + - dns01: + cloudflare: + email: abuxliu@gmail.com + apiTokenSecretRef: + name: cloudflare-api-token-secret + key: api-token diff --git a/ResearchOps/cert-manager/manifest.yaml b/ResearchOps/cert-manager/manifest.yaml new file mode 100644 index 0000000..396ddfa --- /dev/null +++ b/ResearchOps/cert-manager/manifest.yaml @@ -0,0 +1,5402 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: certificaterequests.cert-manager.io + labels: + app: 'cert-manager' + app.kubernetes.io/name: 'cert-manager' + app.kubernetes.io/instance: 'cert-manager' + app.kubernetes.io/version: "v1.9.1" +spec: + group: cert-manager.io + names: + kind: CertificateRequest + listKind: CertificateRequestList + plural: certificaterequests + shortNames: + - cr + - crs + singular: certificaterequest + categories: + - cert-manager + scope: Namespaced + versions: + - name: v1 + subresources: + status: {} + additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=="Approved")].status + name: Approved + type: string + - jsonPath: .status.conditions[?(@.type=="Denied")].status + name: Denied + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + - jsonPath: .spec.issuerRef.name + name: Issuer + type: string + - jsonPath: .spec.username + name: Requestor + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].message + name: Status + priority: 1 + type: string + - jsonPath: .metadata.creationTimestamp + description: CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC. + name: Age + type: date + schema: + openAPIV3Schema: + description: "A CertificateRequest is used to request a signed certificate from one of the configured issuers. \n All fields within the CertificateRequest's `spec` are immutable after creation. A CertificateRequest will either succeed or fail, as denoted by its `status.state` field. \n A CertificateRequest is a one-shot resource, meaning it represents a single point in time request for a certificate and cannot be re-used." + type: object + required: + - spec + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Desired state of the CertificateRequest resource. + type: object + required: + - issuerRef + - request + properties: + duration: + description: The requested 'duration' (i.e. lifetime) of the Certificate. This option may be ignored/overridden by some issuer types. + type: string + extra: + description: Extra contains extra attributes of the user that created the CertificateRequest. Populated by the cert-manager webhook on creation and immutable. + type: object + additionalProperties: + type: array + items: + type: string + groups: + description: Groups contains group membership of the user that created the CertificateRequest. Populated by the cert-manager webhook on creation and immutable. + type: array + items: + type: string + x-kubernetes-list-type: atomic + isCA: + description: IsCA will request to mark the certificate as valid for certificate signing when submitting to the issuer. This will automatically add the `cert sign` usage to the list of `usages`. + type: boolean + issuerRef: + description: IssuerRef is a reference to the issuer for this CertificateRequest. If the `kind` field is not set, or set to `Issuer`, an Issuer resource with the given name in the same namespace as the CertificateRequest will be used. If the `kind` field is set to `ClusterIssuer`, a ClusterIssuer with the provided name will be used. The `name` field in this stanza is required at all times. The group field refers to the API group of the issuer which defaults to `cert-manager.io` if empty. + type: object + required: + - name + properties: + group: + description: Group of the resource being referred to. + type: string + kind: + description: Kind of the resource being referred to. + type: string + name: + description: Name of the resource being referred to. + type: string + request: + description: The PEM-encoded x509 certificate signing request to be submitted to the CA for signing. + type: string + format: byte + uid: + description: UID contains the uid of the user that created the CertificateRequest. Populated by the cert-manager webhook on creation and immutable. + type: string + usages: + description: Usages is the set of x509 usages that are requested for the certificate. If usages are set they SHOULD be encoded inside the CSR spec Defaults to `digital signature` and `key encipherment` if not specified. + type: array + items: + description: 'KeyUsage specifies valid usage contexts for keys. See: https://tools.ietf.org/html/rfc5280#section-4.2.1.3 https://tools.ietf.org/html/rfc5280#section-4.2.1.12 Valid KeyUsage values are as follows: "signing", "digital signature", "content commitment", "key encipherment", "key agreement", "data encipherment", "cert sign", "crl sign", "encipher only", "decipher only", "any", "server auth", "client auth", "code signing", "email protection", "s/mime", "ipsec end system", "ipsec tunnel", "ipsec user", "timestamping", "ocsp signing", "microsoft sgc", "netscape sgc"' + type: string + enum: + - signing + - digital signature + - content commitment + - key encipherment + - key agreement + - data encipherment + - cert sign + - crl sign + - encipher only + - decipher only + - any + - server auth + - client auth + - code signing + - email protection + - s/mime + - ipsec end system + - ipsec tunnel + - ipsec user + - timestamping + - ocsp signing + - microsoft sgc + - netscape sgc + username: + description: Username contains the name of the user that created the CertificateRequest. Populated by the cert-manager webhook on creation and immutable. + type: string + status: + description: Status of the CertificateRequest. This is set and managed automatically. + type: object + properties: + ca: + description: The PEM encoded x509 certificate of the signer, also known as the CA (Certificate Authority). This is set on a best-effort basis by different issuers. If not set, the CA is assumed to be unknown/not available. + type: string + format: byte + certificate: + description: The PEM encoded x509 certificate resulting from the certificate signing request. If not set, the CertificateRequest has either not been completed or has failed. More information on failure can be found by checking the `conditions` field. + type: string + format: byte + conditions: + description: List of status conditions to indicate the status of a CertificateRequest. Known condition types are `Ready` and `InvalidRequest`. + type: array + items: + description: CertificateRequestCondition contains condition information for a CertificateRequest. + type: object + required: + - status + - type + properties: + lastTransitionTime: + description: LastTransitionTime is the timestamp corresponding to the last status change of this condition. + type: string + format: date-time + message: + description: Message is a human readable description of the details of the last transition, complementing reason. + type: string + reason: + description: Reason is a brief machine readable explanation for the condition's last transition. + type: string + status: + description: Status of the condition, one of (`True`, `False`, `Unknown`). + type: string + enum: + - "True" + - "False" + - Unknown + type: + description: Type of the condition, known values are (`Ready`, `InvalidRequest`, `Approved`, `Denied`). + type: string + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + failureTime: + description: FailureTime stores the time that this CertificateRequest failed. This is used to influence garbage collection and back-off. + type: string + format: date-time + served: true + storage: true +--- +# Source: cert-manager/templates/crd-templates.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: certificates.cert-manager.io + labels: + app: 'cert-manager' + app.kubernetes.io/name: 'cert-manager' + app.kubernetes.io/instance: 'cert-manager' + # Generated labels + app.kubernetes.io/version: "v1.9.1" +spec: + group: cert-manager.io + names: + kind: Certificate + listKind: CertificateList + plural: certificates + shortNames: + - cert + - certs + singular: certificate + categories: + - cert-manager + scope: Namespaced + versions: + - name: v1 + subresources: + status: {} + additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + - jsonPath: .spec.secretName + name: Secret + type: string + - jsonPath: .spec.issuerRef.name + name: Issuer + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].message + name: Status + priority: 1 + type: string + - jsonPath: .metadata.creationTimestamp + description: CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC. + name: Age + type: date + schema: + openAPIV3Schema: + description: "A Certificate resource should be created to ensure an up to date and signed x509 certificate is stored in the Kubernetes Secret resource named in `spec.secretName`. \n The stored certificate will be renewed before it expires (as configured by `spec.renewBefore`)." + type: object + required: + - spec + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Desired state of the Certificate resource. + type: object + required: + - issuerRef + - secretName + properties: + additionalOutputFormats: + description: AdditionalOutputFormats defines extra output formats of the private key and signed certificate chain to be written to this Certificate's target Secret. This is an Alpha Feature and is only enabled with the `--feature-gates=AdditionalCertificateOutputFormats=true` option on both the controller and webhook components. + type: array + items: + description: CertificateAdditionalOutputFormat defines an additional output format of a Certificate resource. These contain supplementary data formats of the signed certificate chain and paired private key. + type: object + required: + - type + properties: + type: + description: Type is the name of the format type that should be written to the Certificate's target Secret. + type: string + enum: + - DER + - CombinedPEM + commonName: + description: 'CommonName is a common name to be used on the Certificate. The CommonName should have a length of 64 characters or fewer to avoid generating invalid CSRs. This value is ignored by TLS clients when any subject alt name is set. This is x509 behaviour: https://tools.ietf.org/html/rfc6125#section-6.4.4' + type: string + dnsNames: + description: DNSNames is a list of DNS subjectAltNames to be set on the Certificate. + type: array + items: + type: string + duration: + description: The requested 'duration' (i.e. lifetime) of the Certificate. This option may be ignored/overridden by some issuer types. If unset this defaults to 90 days. Certificate will be renewed either 2/3 through its duration or `renewBefore` period before its expiry, whichever is later. Minimum accepted duration is 1 hour. Value must be in units accepted by Go time.ParseDuration https://golang.org/pkg/time/#ParseDuration + type: string + emailAddresses: + description: EmailAddresses is a list of email subjectAltNames to be set on the Certificate. + type: array + items: + type: string + encodeUsagesInRequest: + description: EncodeUsagesInRequest controls whether key usages should be present in the CertificateRequest + type: boolean + ipAddresses: + description: IPAddresses is a list of IP address subjectAltNames to be set on the Certificate. + type: array + items: + type: string + isCA: + description: IsCA will mark this Certificate as valid for certificate signing. This will automatically add the `cert sign` usage to the list of `usages`. + type: boolean + issuerRef: + description: IssuerRef is a reference to the issuer for this certificate. If the `kind` field is not set, or set to `Issuer`, an Issuer resource with the given name in the same namespace as the Certificate will be used. If the `kind` field is set to `ClusterIssuer`, a ClusterIssuer with the provided name will be used. The `name` field in this stanza is required at all times. + type: object + required: + - name + properties: + group: + description: Group of the resource being referred to. + type: string + kind: + description: Kind of the resource being referred to. + type: string + name: + description: Name of the resource being referred to. + type: string + keystores: + description: Keystores configures additional keystore output formats stored in the `secretName` Secret resource. + type: object + properties: + jks: + description: JKS configures options for storing a JKS keystore in the `spec.secretName` Secret resource. + type: object + required: + - create + - passwordSecretRef + properties: + create: + description: Create enables JKS keystore creation for the Certificate. If true, a file named `keystore.jks` will be created in the target Secret resource, encrypted using the password stored in `passwordSecretRef`. The keystore file will only be updated upon re-issuance. A file named `truststore.jks` will also be created in the target Secret resource, encrypted using the password stored in `passwordSecretRef` containing the issuing Certificate Authority + type: boolean + passwordSecretRef: + description: PasswordSecretRef is a reference to a key in a Secret resource containing the password used to encrypt the JKS keystore. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + pkcs12: + description: PKCS12 configures options for storing a PKCS12 keystore in the `spec.secretName` Secret resource. + type: object + required: + - create + - passwordSecretRef + properties: + create: + description: Create enables PKCS12 keystore creation for the Certificate. If true, a file named `keystore.p12` will be created in the target Secret resource, encrypted using the password stored in `passwordSecretRef`. The keystore file will only be updated upon re-issuance. A file named `truststore.p12` will also be created in the target Secret resource, encrypted using the password stored in `passwordSecretRef` containing the issuing Certificate Authority + type: boolean + passwordSecretRef: + description: PasswordSecretRef is a reference to a key in a Secret resource containing the password used to encrypt the PKCS12 keystore. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + literalSubject: + description: LiteralSubject is an LDAP formatted string that represents the [X.509 Subject field](https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.6). Use this *instead* of the Subject field if you need to ensure the correct ordering of the RDN sequence, such as when issuing certs for LDAP authentication. See https://github.com/cert-manager/cert-manager/issues/3203, https://github.com/cert-manager/cert-manager/issues/4424. This field is alpha level and is only supported by cert-manager installations where LiteralCertificateSubject feature gate is enabled on both cert-manager controller and webhook. + type: string + privateKey: + description: Options to control private keys used for the Certificate. + type: object + properties: + algorithm: + description: Algorithm is the private key algorithm of the corresponding private key for this certificate. If provided, allowed values are either `RSA`,`Ed25519` or `ECDSA` If `algorithm` is specified and `size` is not provided, key size of 256 will be used for `ECDSA` key algorithm and key size of 2048 will be used for `RSA` key algorithm. key size is ignored when using the `Ed25519` key algorithm. + type: string + enum: + - RSA + - ECDSA + - Ed25519 + encoding: + description: The private key cryptography standards (PKCS) encoding for this certificate's private key to be encoded in. If provided, allowed values are `PKCS1` and `PKCS8` standing for PKCS#1 and PKCS#8, respectively. Defaults to `PKCS1` if not specified. + type: string + enum: + - PKCS1 + - PKCS8 + rotationPolicy: + description: RotationPolicy controls how private keys should be regenerated when a re-issuance is being processed. If set to Never, a private key will only be generated if one does not already exist in the target `spec.secretName`. If one does exists but it does not have the correct algorithm or size, a warning will be raised to await user intervention. If set to Always, a private key matching the specified requirements will be generated whenever a re-issuance occurs. Default is 'Never' for backward compatibility. + type: string + enum: + - Never + - Always + size: + description: Size is the key bit size of the corresponding private key for this certificate. If `algorithm` is set to `RSA`, valid values are `2048`, `4096` or `8192`, and will default to `2048` if not specified. If `algorithm` is set to `ECDSA`, valid values are `256`, `384` or `521`, and will default to `256` if not specified. If `algorithm` is set to `Ed25519`, Size is ignored. No other values are allowed. + type: integer + renewBefore: + description: How long before the currently issued certificate's expiry cert-manager should renew the certificate. The default is 2/3 of the issued certificate's duration. Minimum accepted value is 5 minutes. Value must be in units accepted by Go time.ParseDuration https://golang.org/pkg/time/#ParseDuration + type: string + revisionHistoryLimit: + description: revisionHistoryLimit is the maximum number of CertificateRequest revisions that are maintained in the Certificate's history. Each revision represents a single `CertificateRequest` created by this Certificate, either when it was created, renewed, or Spec was changed. Revisions will be removed by oldest first if the number of revisions exceeds this number. If set, revisionHistoryLimit must be a value of `1` or greater. If unset (`nil`), revisions will not be garbage collected. Default value is `nil`. + type: integer + format: int32 + secretName: + description: SecretName is the name of the secret resource that will be automatically created and managed by this Certificate resource. It will be populated with a private key and certificate, signed by the denoted issuer. + type: string + secretTemplate: + description: SecretTemplate defines annotations and labels to be copied to the Certificate's Secret. Labels and annotations on the Secret will be changed as they appear on the SecretTemplate when added or removed. SecretTemplate annotations are added in conjunction with, and cannot overwrite, the base set of annotations cert-manager sets on the Certificate's Secret. + type: object + properties: + annotations: + description: Annotations is a key value map to be copied to the target Kubernetes Secret. + type: object + additionalProperties: + type: string + labels: + description: Labels is a key value map to be copied to the target Kubernetes Secret. + type: object + additionalProperties: + type: string + subject: + description: Full X509 name specification (https://golang.org/pkg/crypto/x509/pkix/#Name). + type: object + properties: + countries: + description: Countries to be used on the Certificate. + type: array + items: + type: string + localities: + description: Cities to be used on the Certificate. + type: array + items: + type: string + organizationalUnits: + description: Organizational Units to be used on the Certificate. + type: array + items: + type: string + organizations: + description: Organizations to be used on the Certificate. + type: array + items: + type: string + postalCodes: + description: Postal codes to be used on the Certificate. + type: array + items: + type: string + provinces: + description: State/Provinces to be used on the Certificate. + type: array + items: + type: string + serialNumber: + description: Serial number to be used on the Certificate. + type: string + streetAddresses: + description: Street addresses to be used on the Certificate. + type: array + items: + type: string + uris: + description: URIs is a list of URI subjectAltNames to be set on the Certificate. + type: array + items: + type: string + usages: + description: Usages is the set of x509 usages that are requested for the certificate. Defaults to `digital signature` and `key encipherment` if not specified. + type: array + items: + description: 'KeyUsage specifies valid usage contexts for keys. See: https://tools.ietf.org/html/rfc5280#section-4.2.1.3 https://tools.ietf.org/html/rfc5280#section-4.2.1.12 Valid KeyUsage values are as follows: "signing", "digital signature", "content commitment", "key encipherment", "key agreement", "data encipherment", "cert sign", "crl sign", "encipher only", "decipher only", "any", "server auth", "client auth", "code signing", "email protection", "s/mime", "ipsec end system", "ipsec tunnel", "ipsec user", "timestamping", "ocsp signing", "microsoft sgc", "netscape sgc"' + type: string + enum: + - signing + - digital signature + - content commitment + - key encipherment + - key agreement + - data encipherment + - cert sign + - crl sign + - encipher only + - decipher only + - any + - server auth + - client auth + - code signing + - email protection + - s/mime + - ipsec end system + - ipsec tunnel + - ipsec user + - timestamping + - ocsp signing + - microsoft sgc + - netscape sgc + status: + description: Status of the Certificate. This is set and managed automatically. + type: object + properties: + conditions: + description: List of status conditions to indicate the status of certificates. Known condition types are `Ready` and `Issuing`. + type: array + items: + description: CertificateCondition contains condition information for an Certificate. + type: object + required: + - status + - type + properties: + lastTransitionTime: + description: LastTransitionTime is the timestamp corresponding to the last status change of this condition. + type: string + format: date-time + message: + description: Message is a human readable description of the details of the last transition, complementing reason. + type: string + observedGeneration: + description: If set, this represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.condition[x].observedGeneration is 9, the condition is out of date with respect to the current state of the Certificate. + type: integer + format: int64 + reason: + description: Reason is a brief machine readable explanation for the condition's last transition. + type: string + status: + description: Status of the condition, one of (`True`, `False`, `Unknown`). + type: string + enum: + - "True" + - "False" + - Unknown + type: + description: Type of the condition, known values are (`Ready`, `Issuing`). + type: string + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + failedIssuanceAttempts: + description: The number of continuous failed issuance attempts up till now. This field gets removed (if set) on a successful issuance and gets set to 1 if unset and an issuance has failed. If an issuance has failed, the delay till the next issuance will be calculated using formula time.Hour * 2 ^ (failedIssuanceAttempts - 1). + type: integer + lastFailureTime: + description: LastFailureTime is the time as recorded by the Certificate controller of the most recent failure to complete a CertificateRequest for this Certificate resource. If set, cert-manager will not re-request another Certificate until 1 hour has elapsed from this time. + type: string + format: date-time + nextPrivateKeySecretName: + description: The name of the Secret resource containing the private key to be used for the next certificate iteration. The keymanager controller will automatically set this field if the `Issuing` condition is set to `True`. It will automatically unset this field when the Issuing condition is not set or False. + type: string + notAfter: + description: The expiration time of the certificate stored in the secret named by this resource in `spec.secretName`. + type: string + format: date-time + notBefore: + description: The time after which the certificate stored in the secret named by this resource in spec.secretName is valid. + type: string + format: date-time + renewalTime: + description: RenewalTime is the time at which the certificate will be next renewed. If not set, no upcoming renewal is scheduled. + type: string + format: date-time + revision: + description: "The current 'revision' of the certificate as issued. \n When a CertificateRequest resource is created, it will have the `cert-manager.io/certificate-revision` set to one greater than the current value of this field. \n Upon issuance, this field will be set to the value of the annotation on the CertificateRequest resource used to issue the certificate. \n Persisting the value on the CertificateRequest resource allows the certificates controller to know whether a request is part of an old issuance or if it is part of the ongoing revision's issuance by checking if the revision value in the annotation is greater than this field." + type: integer + served: true + storage: true +--- +# Source: cert-manager/templates/crd-templates.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: challenges.acme.cert-manager.io + labels: + app: 'cert-manager' + app.kubernetes.io/name: 'cert-manager' + app.kubernetes.io/instance: 'cert-manager' + # Generated labels + app.kubernetes.io/version: "v1.9.1" +spec: + group: acme.cert-manager.io + names: + kind: Challenge + listKind: ChallengeList + plural: challenges + singular: challenge + categories: + - cert-manager + - cert-manager-acme + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.state + name: State + type: string + - jsonPath: .spec.dnsName + name: Domain + type: string + - jsonPath: .status.reason + name: Reason + priority: 1 + type: string + - description: CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC. + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: Challenge is a type to represent a Challenge request with an ACME server + type: object + required: + - metadata + - spec + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + type: object + required: + - authorizationURL + - dnsName + - issuerRef + - key + - solver + - token + - type + - url + properties: + authorizationURL: + description: The URL to the ACME Authorization resource that this challenge is a part of. + type: string + dnsName: + description: dnsName is the identifier that this challenge is for, e.g. example.com. If the requested DNSName is a 'wildcard', this field MUST be set to the non-wildcard domain, e.g. for `*.example.com`, it must be `example.com`. + type: string + issuerRef: + description: References a properly configured ACME-type Issuer which should be used to create this Challenge. If the Issuer does not exist, processing will be retried. If the Issuer is not an 'ACME' Issuer, an error will be returned and the Challenge will be marked as failed. + type: object + required: + - name + properties: + group: + description: Group of the resource being referred to. + type: string + kind: + description: Kind of the resource being referred to. + type: string + name: + description: Name of the resource being referred to. + type: string + key: + description: 'The ACME challenge key for this challenge For HTTP01 challenges, this is the value that must be responded with to complete the HTTP01 challenge in the format: `.`. For DNS01 challenges, this is the base64 encoded SHA256 sum of the `.` text that must be set as the TXT record content.' + type: string + solver: + description: Contains the domain solving configuration that should be used to solve this challenge resource. + type: object + properties: + dns01: + description: Configures cert-manager to attempt to complete authorizations by performing the DNS01 challenge flow. + type: object + properties: + acmeDNS: + description: Use the 'ACME DNS' (https://github.com/joohoi/acme-dns) API to manage DNS01 challenge records. + type: object + required: + - accountSecretRef + - host + properties: + accountSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + host: + type: string + akamai: + description: Use the Akamai DNS zone management API to manage DNS01 challenge records. + type: object + required: + - accessTokenSecretRef + - clientSecretSecretRef + - clientTokenSecretRef + - serviceConsumerDomain + properties: + accessTokenSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + clientSecretSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + clientTokenSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + serviceConsumerDomain: + type: string + azureDNS: + description: Use the Microsoft Azure DNS API to manage DNS01 challenge records. + type: object + required: + - resourceGroupName + - subscriptionID + properties: + clientID: + description: if both this and ClientSecret are left unset MSI will be used + type: string + clientSecretSecretRef: + description: if both this and ClientID are left unset MSI will be used + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + environment: + description: name of the Azure environment (default AzurePublicCloud) + type: string + enum: + - AzurePublicCloud + - AzureChinaCloud + - AzureGermanCloud + - AzureUSGovernmentCloud + hostedZoneName: + description: name of the DNS zone that should be used + type: string + managedIdentity: + description: managed identity configuration, can not be used at the same time as clientID, clientSecretSecretRef or tenantID + type: object + properties: + clientID: + description: client ID of the managed identity, can not be used at the same time as resourceID + type: string + resourceID: + description: resource ID of the managed identity, can not be used at the same time as clientID + type: string + resourceGroupName: + description: resource group the DNS zone is located in + type: string + subscriptionID: + description: ID of the Azure subscription + type: string + tenantID: + description: when specifying ClientID and ClientSecret then this field is also needed + type: string + cloudDNS: + description: Use the Google Cloud DNS API to manage DNS01 challenge records. + type: object + required: + - project + properties: + hostedZoneName: + description: HostedZoneName is an optional field that tells cert-manager in which Cloud DNS zone the challenge record has to be created. If left empty cert-manager will automatically choose a zone. + type: string + project: + type: string + serviceAccountSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + cloudflare: + description: Use the Cloudflare API to manage DNS01 challenge records. + type: object + properties: + apiKeySecretRef: + description: 'API key to use to authenticate with Cloudflare. Note: using an API token to authenticate is now the recommended method as it allows greater control of permissions.' + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + apiTokenSecretRef: + description: API token used to authenticate with Cloudflare. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + email: + description: Email of the account, only required when using API key based authentication. + type: string + cnameStrategy: + description: CNAMEStrategy configures how the DNS01 provider should handle CNAME records when found in DNS zones. + type: string + enum: + - None + - Follow + digitalocean: + description: Use the DigitalOcean DNS API to manage DNS01 challenge records. + type: object + required: + - tokenSecretRef + properties: + tokenSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + rfc2136: + description: Use RFC2136 ("Dynamic Updates in the Domain Name System") (https://datatracker.ietf.org/doc/rfc2136/) to manage DNS01 challenge records. + type: object + required: + - nameserver + properties: + nameserver: + description: The IP address or hostname of an authoritative DNS server supporting RFC2136 in the form host:port. If the host is an IPv6 address it must be enclosed in square brackets (e.g [2001:db8::1]) ; port is optional. This field is required. + type: string + tsigAlgorithm: + description: 'The TSIG Algorithm configured in the DNS supporting RFC2136. Used only when ``tsigSecretSecretRef`` and ``tsigKeyName`` are defined. Supported values are (case-insensitive): ``HMACMD5`` (default), ``HMACSHA1``, ``HMACSHA256`` or ``HMACSHA512``.' + type: string + tsigKeyName: + description: The TSIG Key name configured in the DNS. If ``tsigSecretSecretRef`` is defined, this field is required. + type: string + tsigSecretSecretRef: + description: The name of the secret containing the TSIG value. If ``tsigKeyName`` is defined, this field is required. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + route53: + description: Use the AWS Route53 API to manage DNS01 challenge records. + type: object + required: + - region + properties: + accessKeyID: + description: 'The AccessKeyID is used for authentication. Cannot be set when SecretAccessKeyID is set. If neither the Access Key nor Key ID are set, we fall-back to using env vars, shared credentials file or AWS Instance metadata, see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials' + type: string + accessKeyIDSecretRef: + description: 'The SecretAccessKey is used for authentication. If set, pull the AWS access key ID from a key within a Kubernetes Secret. Cannot be set when AccessKeyID is set. If neither the Access Key nor Key ID are set, we fall-back to using env vars, shared credentials file or AWS Instance metadata, see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials' + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + hostedZoneID: + description: If set, the provider will manage only this zone in Route53 and will not do an lookup using the route53:ListHostedZonesByName api call. + type: string + region: + description: Always set the region when using AccessKeyID and SecretAccessKey + type: string + role: + description: Role is a Role ARN which the Route53 provider will assume using either the explicit credentials AccessKeyID/SecretAccessKey or the inferred credentials from environment variables, shared credentials file or AWS Instance metadata + type: string + secretAccessKeySecretRef: + description: 'The SecretAccessKey is used for authentication. If neither the Access Key nor Key ID are set, we fall-back to using env vars, shared credentials file or AWS Instance metadata, see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials' + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + webhook: + description: Configure an external webhook based DNS01 challenge solver to manage DNS01 challenge records. + type: object + required: + - groupName + - solverName + properties: + config: + description: Additional configuration that should be passed to the webhook apiserver when challenges are processed. This can contain arbitrary JSON data. Secret values should not be specified in this stanza. If secret values are needed (e.g. credentials for a DNS service), you should use a SecretKeySelector to reference a Secret resource. For details on the schema of this field, consult the webhook provider implementation's documentation. + x-kubernetes-preserve-unknown-fields: true + groupName: + description: The API group name that should be used when POSTing ChallengePayload resources to the webhook apiserver. This should be the same as the GroupName specified in the webhook provider implementation. + type: string + solverName: + description: The name of the solver to use, as defined in the webhook provider implementation. This will typically be the name of the provider, e.g. 'cloudflare'. + type: string + http01: + description: Configures cert-manager to attempt to complete authorizations by performing the HTTP01 challenge flow. It is not possible to obtain certificates for wildcard domain names (e.g. `*.example.com`) using the HTTP01 challenge mechanism. + type: object + properties: + gatewayHTTPRoute: + description: The Gateway API is a sig-network community API that models service networking in Kubernetes (https://gateway-api.sigs.k8s.io/). The Gateway solver will create HTTPRoutes with the specified labels in the same namespace as the challenge. This solver is experimental, and fields / behaviour may change in the future. + type: object + properties: + labels: + description: Custom labels that will be applied to HTTPRoutes created by cert-manager while solving HTTP-01 challenges. + type: object + additionalProperties: + type: string + parentRefs: + description: 'When solving an HTTP-01 challenge, cert-manager creates an HTTPRoute. cert-manager needs to know which parentRefs should be used when creating the HTTPRoute. Usually, the parentRef references a Gateway. See: https://gateway-api.sigs.k8s.io/v1alpha2/api-types/httproute/#attaching-to-gateways' + type: array + items: + description: "ParentRef identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid. \n References to objects with invalid Group and Kind are not valid, and must be rejected by the implementation, with appropriate Conditions set on the containing object." + type: object + required: + - name + properties: + group: + description: "Group is the group of the referent. \n Support: Core" + type: string + default: gateway.networking.k8s.io + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + kind: + description: "Kind is kind of the referent. \n Support: Core (Gateway) Support: Custom (Other Resources)" + type: string + default: Gateway + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + name: + description: "Name is the name of the referent. \n Support: Core" + type: string + maxLength: 253 + minLength: 1 + namespace: + description: "Namespace is the namespace of the referent. When unspecified (or empty string), this refers to the local namespace of the Route. \n Support: Core" + type: string + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + type: string + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + serviceType: + description: Optional service type for Kubernetes solver service. Supported values are NodePort or ClusterIP. If unset, defaults to NodePort. + type: string + ingress: + description: The ingress based HTTP01 challenge solver will solve challenges by creating or modifying Ingress resources in order to route requests for '/.well-known/acme-challenge/XYZ' to 'challenge solver' pods that are provisioned by cert-manager for each Challenge to be completed. + type: object + properties: + class: + description: The ingress class to use when creating Ingress resources to solve ACME challenges that use this challenge solver. Only one of 'class' or 'name' may be specified. + type: string + ingressTemplate: + description: Optional ingress template used to configure the ACME challenge solver ingress used for HTTP01 challenges. + type: object + properties: + metadata: + description: ObjectMeta overrides for the ingress used to solve HTTP01 challenges. Only the 'labels' and 'annotations' fields may be set. If labels or annotations overlap with in-built values, the values here will override the in-built values. + type: object + properties: + annotations: + description: Annotations that should be added to the created ACME HTTP01 solver ingress. + type: object + additionalProperties: + type: string + labels: + description: Labels that should be added to the created ACME HTTP01 solver ingress. + type: object + additionalProperties: + type: string + name: + description: The name of the ingress resource that should have ACME challenge solving routes inserted into it in order to solve HTTP01 challenges. This is typically used in conjunction with ingress controllers like ingress-gce, which maintains a 1:1 mapping between external IPs and ingress resources. + type: string + podTemplate: + description: Optional pod template used to configure the ACME challenge solver pods used for HTTP01 challenges. + type: object + properties: + metadata: + description: ObjectMeta overrides for the pod used to solve HTTP01 challenges. Only the 'labels' and 'annotations' fields may be set. If labels or annotations overlap with in-built values, the values here will override the in-built values. + type: object + properties: + annotations: + description: Annotations that should be added to the create ACME HTTP01 solver pods. + type: object + additionalProperties: + type: string + labels: + description: Labels that should be added to the created ACME HTTP01 solver pods. + type: object + additionalProperties: + type: string + spec: + description: PodSpec defines overrides for the HTTP01 challenge solver pod. Only the 'priorityClassName', 'nodeSelector', 'affinity', 'serviceAccountName' and 'tolerations' fields are supported currently. All other fields will be ignored. + type: object + properties: + affinity: + description: If specified, the pod's scheduling constraints + type: object + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for the pod. + type: object + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node matches the corresponding matchExpressions; the node(s) with the highest sum are the most preferred. + type: array + items: + description: An empty preferred scheduling term matches all objects with implicit weight 0 (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + type: object + required: + - preference + - weight + properties: + preference: + description: A node selector term, associated with the corresponding weight. + type: object + properties: + matchExpressions: + description: A list of node selector requirements by node's labels. + type: array + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchFields: + description: A list of node selector requirements by node's fields. + type: array + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + type: array + items: + type: string + weight: + description: Weight associated with matching the corresponding nodeSelectorTerm, in the range 1-100. + type: integer + format: int32 + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to an update), the system may or may not try to eventually evict the pod from its node. + type: object + required: + - nodeSelectorTerms + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. The terms are ORed. + type: array + items: + description: A null or empty node selector term matches no objects. The requirements of them are ANDed. The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + type: object + properties: + matchExpressions: + description: A list of node selector requirements by node's labels. + type: array + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchFields: + description: A list of node selector requirements by node's fields. + type: array + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + type: array + items: + type: string + podAffinity: + description: Describes pod affinity scheduling rules (e.g. co-locate this pod in the same node, zone, etc. as some other pod(s)). + type: object + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. + type: array + items: + description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) + type: object + required: + - podAffinityTerm + - weight + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated with the corresponding weight. + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + weight: + description: weight associated with matching the corresponding podAffinityTerm, in the range 1-100. + type: integer + format: int32 + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied. + type: array + items: + description: Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key matches that of any node on which a pod of the set of pods is running + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod in the same node, zone, etc. as some other pod(s)). + type: object + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to nodes that satisfy the anti-affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. + type: array + items: + description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) + type: object + required: + - podAffinityTerm + - weight + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated with the corresponding weight. + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + weight: + description: weight associated with matching the corresponding podAffinityTerm, in the range 1-100. + type: integer + format: int32 + requiredDuringSchedulingIgnoredDuringExecution: + description: If the anti-affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the anti-affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied. + type: array + items: + description: Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key matches that of any node on which a pod of the set of pods is running + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + nodeSelector: + description: 'NodeSelector is a selector which must be true for the pod to fit on a node. Selector which must match a node''s labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' + type: object + additionalProperties: + type: string + priorityClassName: + description: If specified, the pod's priorityClassName. + type: string + serviceAccountName: + description: If specified, the pod's service account + type: string + tolerations: + description: If specified, the pod's tolerations. + type: array + items: + description: The pod this Toleration is attached to tolerates any taint that matches the triple using the matching operator . + type: object + properties: + effect: + description: Effect indicates the taint effect to match. Empty means match all taint effects. When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies to. Empty means match all taint keys. If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: Operator represents a key's relationship to the value. Valid operators are Exists and Equal. Defaults to Equal. Exists is equivalent to wildcard for value, so that a pod can tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of time the toleration (which must be of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, it is not set, which means tolerate the taint forever (do not evict). Zero and negative values will be treated as 0 (evict immediately) by the system. + type: integer + format: int64 + value: + description: Value is the taint value the toleration matches to. If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + serviceType: + description: Optional service type for Kubernetes solver service. Supported values are NodePort or ClusterIP. If unset, defaults to NodePort. + type: string + selector: + description: Selector selects a set of DNSNames on the Certificate resource that should be solved using this challenge solver. If not specified, the solver will be treated as the 'default' solver with the lowest priority, i.e. if any other solver has a more specific match, it will be used instead. + type: object + properties: + dnsNames: + description: List of DNSNames that this solver will be used to solve. If specified and a match is found, a dnsNames selector will take precedence over a dnsZones selector. If multiple solvers match with the same dnsNames value, the solver with the most matching labels in matchLabels will be selected. If neither has more matches, the solver defined earlier in the list will be selected. + type: array + items: + type: string + dnsZones: + description: List of DNSZones that this solver will be used to solve. The most specific DNS zone match specified here will take precedence over other DNS zone matches, so a solver specifying sys.example.com will be selected over one specifying example.com for the domain www.sys.example.com. If multiple solvers match with the same dnsZones value, the solver with the most matching labels in matchLabels will be selected. If neither has more matches, the solver defined earlier in the list will be selected. + type: array + items: + type: string + matchLabels: + description: A label selector that is used to refine the set of certificate's that this challenge solver will apply to. + type: object + additionalProperties: + type: string + token: + description: The ACME challenge token for this challenge. This is the raw value returned from the ACME server. + type: string + type: + description: The type of ACME challenge this resource represents. One of "HTTP-01" or "DNS-01". + type: string + enum: + - HTTP-01 + - DNS-01 + url: + description: The URL of the ACME Challenge resource for this challenge. This can be used to lookup details about the status of this challenge. + type: string + wildcard: + description: wildcard will be true if this challenge is for a wildcard identifier, for example '*.example.com'. + type: boolean + status: + type: object + properties: + presented: + description: presented will be set to true if the challenge values for this challenge are currently 'presented'. This *does not* imply the self check is passing. Only that the values have been 'submitted' for the appropriate challenge mechanism (i.e. the DNS01 TXT record has been presented, or the HTTP01 configuration has been configured). + type: boolean + processing: + description: Used to denote whether this challenge should be processed or not. This field will only be set to true by the 'scheduling' component. It will only be set to false by the 'challenges' controller, after the challenge has reached a final state or timed out. If this field is set to false, the challenge controller will not take any more action. + type: boolean + reason: + description: Contains human readable information on why the Challenge is in the current state. + type: string + state: + description: Contains the current 'state' of the challenge. If not set, the state of the challenge is unknown. + type: string + enum: + - valid + - ready + - pending + - processing + - invalid + - expired + - errored + served: true + storage: true + subresources: + status: {} +--- +# Source: cert-manager/templates/crd-templates.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: clusterissuers.cert-manager.io + labels: + app: 'cert-manager' + app.kubernetes.io/name: 'cert-manager' + app.kubernetes.io/instance: 'cert-manager' + # Generated labels + app.kubernetes.io/version: "v1.9.1" +spec: + group: cert-manager.io + names: + kind: ClusterIssuer + listKind: ClusterIssuerList + plural: clusterissuers + singular: clusterissuer + categories: + - cert-manager + scope: Cluster + versions: + - name: v1 + subresources: + status: {} + additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].message + name: Status + priority: 1 + type: string + - jsonPath: .metadata.creationTimestamp + description: CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC. + name: Age + type: date + schema: + openAPIV3Schema: + description: A ClusterIssuer represents a certificate issuing authority which can be referenced as part of `issuerRef` fields. It is similar to an Issuer, however it is cluster-scoped and therefore can be referenced by resources that exist in *any* namespace, not just the same namespace as the referent. + type: object + required: + - spec + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Desired state of the ClusterIssuer resource. + type: object + properties: + acme: + description: ACME configures this issuer to communicate with a RFC8555 (ACME) server to obtain signed x509 certificates. + type: object + required: + - privateKeySecretRef + - server + properties: + disableAccountKeyGeneration: + description: Enables or disables generating a new ACME account key. If true, the Issuer resource will *not* request a new account but will expect the account key to be supplied via an existing secret. If false, the cert-manager system will generate a new ACME account key for the Issuer. Defaults to false. + type: boolean + email: + description: Email is the email address to be associated with the ACME account. This field is optional, but it is strongly recommended to be set. It will be used to contact you in case of issues with your account or certificates, including expiry notification emails. This field may be updated after the account is initially registered. + type: string + enableDurationFeature: + description: Enables requesting a Not After date on certificates that matches the duration of the certificate. This is not supported by all ACME servers like Let's Encrypt. If set to true when the ACME server does not support it it will create an error on the Order. Defaults to false. + type: boolean + externalAccountBinding: + description: ExternalAccountBinding is a reference to a CA external account of the ACME server. If set, upon registration cert-manager will attempt to associate the given external account credentials with the registered ACME account. + type: object + required: + - keyID + - keySecretRef + properties: + keyAlgorithm: + description: 'Deprecated: keyAlgorithm field exists for historical compatibility reasons and should not be used. The algorithm is now hardcoded to HS256 in golang/x/crypto/acme.' + type: string + enum: + - HS256 + - HS384 + - HS512 + keyID: + description: keyID is the ID of the CA key that the External Account is bound to. + type: string + keySecretRef: + description: keySecretRef is a Secret Key Selector referencing a data item in a Kubernetes Secret which holds the symmetric MAC key of the External Account Binding. The `key` is the index string that is paired with the key data in the Secret and should not be confused with the key data itself, or indeed with the External Account Binding keyID above. The secret key stored in the Secret **must** be un-padded, base64 URL encoded data. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + preferredChain: + description: 'PreferredChain is the chain to use if the ACME server outputs multiple. PreferredChain is no guarantee that this one gets delivered by the ACME endpoint. For example, for Let''s Encrypt''s DST crosssign you would use: "DST Root CA X3" or "ISRG Root X1" for the newer Let''s Encrypt root CA. This value picks the first certificate bundle in the ACME alternative chains that has a certificate with this value as its issuer''s CN' + type: string + maxLength: 64 + privateKeySecretRef: + description: PrivateKey is the name of a Kubernetes Secret resource that will be used to store the automatically generated ACME account private key. Optionally, a `key` may be specified to select a specific entry within the named Secret resource. If `key` is not specified, a default of `tls.key` will be used. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + server: + description: 'Server is the URL used to access the ACME server''s ''directory'' endpoint. For example, for Let''s Encrypt''s staging endpoint, you would use: "https://acme-staging-v02.api.letsencrypt.org/directory". Only ACME v2 endpoints (i.e. RFC 8555) are supported.' + type: string + skipTLSVerify: + description: Enables or disables validation of the ACME server TLS certificate. If true, requests to the ACME server will not have their TLS certificate validated (i.e. insecure connections will be allowed). Only enable this option in development environments. The cert-manager system installed roots will be used to verify connections to the ACME server if this is false. Defaults to false. + type: boolean + solvers: + description: 'Solvers is a list of challenge solvers that will be used to solve ACME challenges for the matching domains. Solver configurations must be provided in order to obtain certificates from an ACME server. For more information, see: https://cert-manager.io/docs/configuration/acme/' + type: array + items: + description: An ACMEChallengeSolver describes how to solve ACME challenges for the issuer it is part of. A selector may be provided to use different solving strategies for different DNS names. Only one of HTTP01 or DNS01 must be provided. + type: object + properties: + dns01: + description: Configures cert-manager to attempt to complete authorizations by performing the DNS01 challenge flow. + type: object + properties: + acmeDNS: + description: Use the 'ACME DNS' (https://github.com/joohoi/acme-dns) API to manage DNS01 challenge records. + type: object + required: + - accountSecretRef + - host + properties: + accountSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + host: + type: string + akamai: + description: Use the Akamai DNS zone management API to manage DNS01 challenge records. + type: object + required: + - accessTokenSecretRef + - clientSecretSecretRef + - clientTokenSecretRef + - serviceConsumerDomain + properties: + accessTokenSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + clientSecretSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + clientTokenSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + serviceConsumerDomain: + type: string + azureDNS: + description: Use the Microsoft Azure DNS API to manage DNS01 challenge records. + type: object + required: + - resourceGroupName + - subscriptionID + properties: + clientID: + description: if both this and ClientSecret are left unset MSI will be used + type: string + clientSecretSecretRef: + description: if both this and ClientID are left unset MSI will be used + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + environment: + description: name of the Azure environment (default AzurePublicCloud) + type: string + enum: + - AzurePublicCloud + - AzureChinaCloud + - AzureGermanCloud + - AzureUSGovernmentCloud + hostedZoneName: + description: name of the DNS zone that should be used + type: string + managedIdentity: + description: managed identity configuration, can not be used at the same time as clientID, clientSecretSecretRef or tenantID + type: object + properties: + clientID: + description: client ID of the managed identity, can not be used at the same time as resourceID + type: string + resourceID: + description: resource ID of the managed identity, can not be used at the same time as clientID + type: string + resourceGroupName: + description: resource group the DNS zone is located in + type: string + subscriptionID: + description: ID of the Azure subscription + type: string + tenantID: + description: when specifying ClientID and ClientSecret then this field is also needed + type: string + cloudDNS: + description: Use the Google Cloud DNS API to manage DNS01 challenge records. + type: object + required: + - project + properties: + hostedZoneName: + description: HostedZoneName is an optional field that tells cert-manager in which Cloud DNS zone the challenge record has to be created. If left empty cert-manager will automatically choose a zone. + type: string + project: + type: string + serviceAccountSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + cloudflare: + description: Use the Cloudflare API to manage DNS01 challenge records. + type: object + properties: + apiKeySecretRef: + description: 'API key to use to authenticate with Cloudflare. Note: using an API token to authenticate is now the recommended method as it allows greater control of permissions.' + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + apiTokenSecretRef: + description: API token used to authenticate with Cloudflare. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + email: + description: Email of the account, only required when using API key based authentication. + type: string + cnameStrategy: + description: CNAMEStrategy configures how the DNS01 provider should handle CNAME records when found in DNS zones. + type: string + enum: + - None + - Follow + digitalocean: + description: Use the DigitalOcean DNS API to manage DNS01 challenge records. + type: object + required: + - tokenSecretRef + properties: + tokenSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + rfc2136: + description: Use RFC2136 ("Dynamic Updates in the Domain Name System") (https://datatracker.ietf.org/doc/rfc2136/) to manage DNS01 challenge records. + type: object + required: + - nameserver + properties: + nameserver: + description: The IP address or hostname of an authoritative DNS server supporting RFC2136 in the form host:port. If the host is an IPv6 address it must be enclosed in square brackets (e.g [2001:db8::1]) ; port is optional. This field is required. + type: string + tsigAlgorithm: + description: 'The TSIG Algorithm configured in the DNS supporting RFC2136. Used only when ``tsigSecretSecretRef`` and ``tsigKeyName`` are defined. Supported values are (case-insensitive): ``HMACMD5`` (default), ``HMACSHA1``, ``HMACSHA256`` or ``HMACSHA512``.' + type: string + tsigKeyName: + description: The TSIG Key name configured in the DNS. If ``tsigSecretSecretRef`` is defined, this field is required. + type: string + tsigSecretSecretRef: + description: The name of the secret containing the TSIG value. If ``tsigKeyName`` is defined, this field is required. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + route53: + description: Use the AWS Route53 API to manage DNS01 challenge records. + type: object + required: + - region + properties: + accessKeyID: + description: 'The AccessKeyID is used for authentication. Cannot be set when SecretAccessKeyID is set. If neither the Access Key nor Key ID are set, we fall-back to using env vars, shared credentials file or AWS Instance metadata, see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials' + type: string + accessKeyIDSecretRef: + description: 'The SecretAccessKey is used for authentication. If set, pull the AWS access key ID from a key within a Kubernetes Secret. Cannot be set when AccessKeyID is set. If neither the Access Key nor Key ID are set, we fall-back to using env vars, shared credentials file or AWS Instance metadata, see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials' + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + hostedZoneID: + description: If set, the provider will manage only this zone in Route53 and will not do an lookup using the route53:ListHostedZonesByName api call. + type: string + region: + description: Always set the region when using AccessKeyID and SecretAccessKey + type: string + role: + description: Role is a Role ARN which the Route53 provider will assume using either the explicit credentials AccessKeyID/SecretAccessKey or the inferred credentials from environment variables, shared credentials file or AWS Instance metadata + type: string + secretAccessKeySecretRef: + description: 'The SecretAccessKey is used for authentication. If neither the Access Key nor Key ID are set, we fall-back to using env vars, shared credentials file or AWS Instance metadata, see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials' + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + webhook: + description: Configure an external webhook based DNS01 challenge solver to manage DNS01 challenge records. + type: object + required: + - groupName + - solverName + properties: + config: + description: Additional configuration that should be passed to the webhook apiserver when challenges are processed. This can contain arbitrary JSON data. Secret values should not be specified in this stanza. If secret values are needed (e.g. credentials for a DNS service), you should use a SecretKeySelector to reference a Secret resource. For details on the schema of this field, consult the webhook provider implementation's documentation. + x-kubernetes-preserve-unknown-fields: true + groupName: + description: The API group name that should be used when POSTing ChallengePayload resources to the webhook apiserver. This should be the same as the GroupName specified in the webhook provider implementation. + type: string + solverName: + description: The name of the solver to use, as defined in the webhook provider implementation. This will typically be the name of the provider, e.g. 'cloudflare'. + type: string + http01: + description: Configures cert-manager to attempt to complete authorizations by performing the HTTP01 challenge flow. It is not possible to obtain certificates for wildcard domain names (e.g. `*.example.com`) using the HTTP01 challenge mechanism. + type: object + properties: + gatewayHTTPRoute: + description: The Gateway API is a sig-network community API that models service networking in Kubernetes (https://gateway-api.sigs.k8s.io/). The Gateway solver will create HTTPRoutes with the specified labels in the same namespace as the challenge. This solver is experimental, and fields / behaviour may change in the future. + type: object + properties: + labels: + description: Custom labels that will be applied to HTTPRoutes created by cert-manager while solving HTTP-01 challenges. + type: object + additionalProperties: + type: string + parentRefs: + description: 'When solving an HTTP-01 challenge, cert-manager creates an HTTPRoute. cert-manager needs to know which parentRefs should be used when creating the HTTPRoute. Usually, the parentRef references a Gateway. See: https://gateway-api.sigs.k8s.io/v1alpha2/api-types/httproute/#attaching-to-gateways' + type: array + items: + description: "ParentRef identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid. \n References to objects with invalid Group and Kind are not valid, and must be rejected by the implementation, with appropriate Conditions set on the containing object." + type: object + required: + - name + properties: + group: + description: "Group is the group of the referent. \n Support: Core" + type: string + default: gateway.networking.k8s.io + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + kind: + description: "Kind is kind of the referent. \n Support: Core (Gateway) Support: Custom (Other Resources)" + type: string + default: Gateway + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + name: + description: "Name is the name of the referent. \n Support: Core" + type: string + maxLength: 253 + minLength: 1 + namespace: + description: "Namespace is the namespace of the referent. When unspecified (or empty string), this refers to the local namespace of the Route. \n Support: Core" + type: string + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + type: string + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + serviceType: + description: Optional service type for Kubernetes solver service. Supported values are NodePort or ClusterIP. If unset, defaults to NodePort. + type: string + ingress: + description: The ingress based HTTP01 challenge solver will solve challenges by creating or modifying Ingress resources in order to route requests for '/.well-known/acme-challenge/XYZ' to 'challenge solver' pods that are provisioned by cert-manager for each Challenge to be completed. + type: object + properties: + class: + description: The ingress class to use when creating Ingress resources to solve ACME challenges that use this challenge solver. Only one of 'class' or 'name' may be specified. + type: string + ingressTemplate: + description: Optional ingress template used to configure the ACME challenge solver ingress used for HTTP01 challenges. + type: object + properties: + metadata: + description: ObjectMeta overrides for the ingress used to solve HTTP01 challenges. Only the 'labels' and 'annotations' fields may be set. If labels or annotations overlap with in-built values, the values here will override the in-built values. + type: object + properties: + annotations: + description: Annotations that should be added to the created ACME HTTP01 solver ingress. + type: object + additionalProperties: + type: string + labels: + description: Labels that should be added to the created ACME HTTP01 solver ingress. + type: object + additionalProperties: + type: string + name: + description: The name of the ingress resource that should have ACME challenge solving routes inserted into it in order to solve HTTP01 challenges. This is typically used in conjunction with ingress controllers like ingress-gce, which maintains a 1:1 mapping between external IPs and ingress resources. + type: string + podTemplate: + description: Optional pod template used to configure the ACME challenge solver pods used for HTTP01 challenges. + type: object + properties: + metadata: + description: ObjectMeta overrides for the pod used to solve HTTP01 challenges. Only the 'labels' and 'annotations' fields may be set. If labels or annotations overlap with in-built values, the values here will override the in-built values. + type: object + properties: + annotations: + description: Annotations that should be added to the create ACME HTTP01 solver pods. + type: object + additionalProperties: + type: string + labels: + description: Labels that should be added to the created ACME HTTP01 solver pods. + type: object + additionalProperties: + type: string + spec: + description: PodSpec defines overrides for the HTTP01 challenge solver pod. Only the 'priorityClassName', 'nodeSelector', 'affinity', 'serviceAccountName' and 'tolerations' fields are supported currently. All other fields will be ignored. + type: object + properties: + affinity: + description: If specified, the pod's scheduling constraints + type: object + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for the pod. + type: object + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node matches the corresponding matchExpressions; the node(s) with the highest sum are the most preferred. + type: array + items: + description: An empty preferred scheduling term matches all objects with implicit weight 0 (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + type: object + required: + - preference + - weight + properties: + preference: + description: A node selector term, associated with the corresponding weight. + type: object + properties: + matchExpressions: + description: A list of node selector requirements by node's labels. + type: array + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchFields: + description: A list of node selector requirements by node's fields. + type: array + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + type: array + items: + type: string + weight: + description: Weight associated with matching the corresponding nodeSelectorTerm, in the range 1-100. + type: integer + format: int32 + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to an update), the system may or may not try to eventually evict the pod from its node. + type: object + required: + - nodeSelectorTerms + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. The terms are ORed. + type: array + items: + description: A null or empty node selector term matches no objects. The requirements of them are ANDed. The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + type: object + properties: + matchExpressions: + description: A list of node selector requirements by node's labels. + type: array + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchFields: + description: A list of node selector requirements by node's fields. + type: array + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + type: array + items: + type: string + podAffinity: + description: Describes pod affinity scheduling rules (e.g. co-locate this pod in the same node, zone, etc. as some other pod(s)). + type: object + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. + type: array + items: + description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) + type: object + required: + - podAffinityTerm + - weight + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated with the corresponding weight. + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + weight: + description: weight associated with matching the corresponding podAffinityTerm, in the range 1-100. + type: integer + format: int32 + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied. + type: array + items: + description: Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key matches that of any node on which a pod of the set of pods is running + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod in the same node, zone, etc. as some other pod(s)). + type: object + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to nodes that satisfy the anti-affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. + type: array + items: + description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) + type: object + required: + - podAffinityTerm + - weight + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated with the corresponding weight. + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + weight: + description: weight associated with matching the corresponding podAffinityTerm, in the range 1-100. + type: integer + format: int32 + requiredDuringSchedulingIgnoredDuringExecution: + description: If the anti-affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the anti-affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied. + type: array + items: + description: Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key matches that of any node on which a pod of the set of pods is running + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + nodeSelector: + description: 'NodeSelector is a selector which must be true for the pod to fit on a node. Selector which must match a node''s labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' + type: object + additionalProperties: + type: string + priorityClassName: + description: If specified, the pod's priorityClassName. + type: string + serviceAccountName: + description: If specified, the pod's service account + type: string + tolerations: + description: If specified, the pod's tolerations. + type: array + items: + description: The pod this Toleration is attached to tolerates any taint that matches the triple using the matching operator . + type: object + properties: + effect: + description: Effect indicates the taint effect to match. Empty means match all taint effects. When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies to. Empty means match all taint keys. If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: Operator represents a key's relationship to the value. Valid operators are Exists and Equal. Defaults to Equal. Exists is equivalent to wildcard for value, so that a pod can tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of time the toleration (which must be of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, it is not set, which means tolerate the taint forever (do not evict). Zero and negative values will be treated as 0 (evict immediately) by the system. + type: integer + format: int64 + value: + description: Value is the taint value the toleration matches to. If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + serviceType: + description: Optional service type for Kubernetes solver service. Supported values are NodePort or ClusterIP. If unset, defaults to NodePort. + type: string + selector: + description: Selector selects a set of DNSNames on the Certificate resource that should be solved using this challenge solver. If not specified, the solver will be treated as the 'default' solver with the lowest priority, i.e. if any other solver has a more specific match, it will be used instead. + type: object + properties: + dnsNames: + description: List of DNSNames that this solver will be used to solve. If specified and a match is found, a dnsNames selector will take precedence over a dnsZones selector. If multiple solvers match with the same dnsNames value, the solver with the most matching labels in matchLabels will be selected. If neither has more matches, the solver defined earlier in the list will be selected. + type: array + items: + type: string + dnsZones: + description: List of DNSZones that this solver will be used to solve. The most specific DNS zone match specified here will take precedence over other DNS zone matches, so a solver specifying sys.example.com will be selected over one specifying example.com for the domain www.sys.example.com. If multiple solvers match with the same dnsZones value, the solver with the most matching labels in matchLabels will be selected. If neither has more matches, the solver defined earlier in the list will be selected. + type: array + items: + type: string + matchLabels: + description: A label selector that is used to refine the set of certificate's that this challenge solver will apply to. + type: object + additionalProperties: + type: string + ca: + description: CA configures this issuer to sign certificates using a signing CA keypair stored in a Secret resource. This is used to build internal PKIs that are managed by cert-manager. + type: object + required: + - secretName + properties: + crlDistributionPoints: + description: The CRL distribution points is an X.509 v3 certificate extension which identifies the location of the CRL from which the revocation of this certificate can be checked. If not set, certificates will be issued without distribution points set. + type: array + items: + type: string + ocspServers: + description: The OCSP server list is an X.509 v3 extension that defines a list of URLs of OCSP responders. The OCSP responders can be queried for the revocation status of an issued certificate. If not set, the certificate will be issued with no OCSP servers set. For example, an OCSP server URL could be "http://ocsp.int-x3.letsencrypt.org". + type: array + items: + type: string + secretName: + description: SecretName is the name of the secret used to sign Certificates issued by this Issuer. + type: string + selfSigned: + description: SelfSigned configures this issuer to 'self sign' certificates using the private key used to create the CertificateRequest object. + type: object + properties: + crlDistributionPoints: + description: The CRL distribution points is an X.509 v3 certificate extension which identifies the location of the CRL from which the revocation of this certificate can be checked. If not set certificate will be issued without CDP. Values are strings. + type: array + items: + type: string + vault: + description: Vault configures this issuer to sign certificates using a HashiCorp Vault PKI backend. + type: object + required: + - auth + - path + - server + properties: + auth: + description: Auth configures how cert-manager authenticates with the Vault server. + type: object + properties: + appRole: + description: AppRole authenticates with Vault using the App Role auth mechanism, with the role and secret stored in a Kubernetes Secret resource. + type: object + required: + - path + - roleId + - secretRef + properties: + path: + description: 'Path where the App Role authentication backend is mounted in Vault, e.g: "approle"' + type: string + roleId: + description: RoleID configured in the App Role authentication backend when setting up the authentication backend in Vault. + type: string + secretRef: + description: Reference to a key in a Secret that contains the App Role secret used to authenticate with Vault. The `key` field must be specified and denotes which entry within the Secret resource is used as the app role secret. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + kubernetes: + description: Kubernetes authenticates with Vault by passing the ServiceAccount token stored in the named Secret resource to the Vault server. + type: object + required: + - role + - secretRef + properties: + mountPath: + description: The Vault mountPath here is the mount path to use when authenticating with Vault. For example, setting a value to `/v1/auth/foo`, will use the path `/v1/auth/foo/login` to authenticate with Vault. If unspecified, the default value "/v1/auth/kubernetes" will be used. + type: string + role: + description: A required field containing the Vault Role to assume. A Role binds a Kubernetes ServiceAccount with a set of Vault policies. + type: string + secretRef: + description: The required Secret field containing a Kubernetes ServiceAccount JWT used for authenticating with Vault. Use of 'ambient credentials' is not supported. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + tokenSecretRef: + description: TokenSecretRef authenticates with Vault by presenting a token. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + caBundle: + description: PEM-encoded CA bundle (base64-encoded) used to validate Vault server certificate. Only used if the Server URL is using HTTPS protocol. This parameter is ignored for plain HTTP protocol connection. If not set the system root certificates are used to validate the TLS connection. + type: string + format: byte + namespace: + description: 'Name of the vault namespace. Namespaces is a set of features within Vault Enterprise that allows Vault environments to support Secure Multi-tenancy. e.g: "ns1" More about namespaces can be found here https://www.vaultproject.io/docs/enterprise/namespaces' + type: string + path: + description: 'Path is the mount path of the Vault PKI backend''s `sign` endpoint, e.g: "my_pki_mount/sign/my-role-name".' + type: string + server: + description: 'Server is the connection address for the Vault server, e.g: "https://vault.example.com:8200".' + type: string + venafi: + description: Venafi configures this issuer to sign certificates using a Venafi TPP or Venafi Cloud policy zone. + type: object + required: + - zone + properties: + cloud: + description: Cloud specifies the Venafi cloud configuration settings. Only one of TPP or Cloud may be specified. + type: object + required: + - apiTokenSecretRef + properties: + apiTokenSecretRef: + description: APITokenSecretRef is a secret key selector for the Venafi Cloud API token. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + url: + description: URL is the base URL for Venafi Cloud. Defaults to "https://api.venafi.cloud/v1". + type: string + tpp: + description: TPP specifies Trust Protection Platform configuration settings. Only one of TPP or Cloud may be specified. + type: object + required: + - credentialsRef + - url + properties: + caBundle: + description: CABundle is a PEM encoded TLS certificate to use to verify connections to the TPP instance. If specified, system roots will not be used and the issuing CA for the TPP instance must be verifiable using the provided root. If not specified, the connection will be verified using the cert-manager system root certificates. + type: string + format: byte + credentialsRef: + description: CredentialsRef is a reference to a Secret containing the username and password for the TPP server. The secret must contain two keys, 'username' and 'password'. + type: object + required: + - name + properties: + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + url: + description: 'URL is the base URL for the vedsdk endpoint of the Venafi TPP instance, for example: "https://tpp.example.com/vedsdk".' + type: string + zone: + description: Zone is the Venafi Policy Zone to use for this issuer. All requests made to the Venafi platform will be restricted by the named zone policy. This field is required. + type: string + status: + description: Status of the ClusterIssuer. This is set and managed automatically. + type: object + properties: + acme: + description: ACME specific status options. This field should only be set if the Issuer is configured to use an ACME server to issue certificates. + type: object + properties: + lastRegisteredEmail: + description: LastRegisteredEmail is the email associated with the latest registered ACME account, in order to track changes made to registered account associated with the Issuer + type: string + uri: + description: URI is the unique account identifier, which can also be used to retrieve account details from the CA + type: string + conditions: + description: List of status conditions to indicate the status of a CertificateRequest. Known condition types are `Ready`. + type: array + items: + description: IssuerCondition contains condition information for an Issuer. + type: object + required: + - status + - type + properties: + lastTransitionTime: + description: LastTransitionTime is the timestamp corresponding to the last status change of this condition. + type: string + format: date-time + message: + description: Message is a human readable description of the details of the last transition, complementing reason. + type: string + observedGeneration: + description: If set, this represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.condition[x].observedGeneration is 9, the condition is out of date with respect to the current state of the Issuer. + type: integer + format: int64 + reason: + description: Reason is a brief machine readable explanation for the condition's last transition. + type: string + status: + description: Status of the condition, one of (`True`, `False`, `Unknown`). + type: string + enum: + - "True" + - "False" + - Unknown + type: + description: Type of the condition, known values are (`Ready`). + type: string + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + served: true + storage: true +--- +# Source: cert-manager/templates/crd-templates.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: issuers.cert-manager.io + labels: + app: 'cert-manager' + app.kubernetes.io/name: 'cert-manager' + app.kubernetes.io/instance: 'cert-manager' + # Generated labels + app.kubernetes.io/version: "v1.9.1" +spec: + group: cert-manager.io + names: + kind: Issuer + listKind: IssuerList + plural: issuers + singular: issuer + categories: + - cert-manager + scope: Namespaced + versions: + - name: v1 + subresources: + status: {} + additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].message + name: Status + priority: 1 + type: string + - jsonPath: .metadata.creationTimestamp + description: CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC. + name: Age + type: date + schema: + openAPIV3Schema: + description: An Issuer represents a certificate issuing authority which can be referenced as part of `issuerRef` fields. It is scoped to a single namespace and can therefore only be referenced by resources within the same namespace. + type: object + required: + - spec + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Desired state of the Issuer resource. + type: object + properties: + acme: + description: ACME configures this issuer to communicate with a RFC8555 (ACME) server to obtain signed x509 certificates. + type: object + required: + - privateKeySecretRef + - server + properties: + disableAccountKeyGeneration: + description: Enables or disables generating a new ACME account key. If true, the Issuer resource will *not* request a new account but will expect the account key to be supplied via an existing secret. If false, the cert-manager system will generate a new ACME account key for the Issuer. Defaults to false. + type: boolean + email: + description: Email is the email address to be associated with the ACME account. This field is optional, but it is strongly recommended to be set. It will be used to contact you in case of issues with your account or certificates, including expiry notification emails. This field may be updated after the account is initially registered. + type: string + enableDurationFeature: + description: Enables requesting a Not After date on certificates that matches the duration of the certificate. This is not supported by all ACME servers like Let's Encrypt. If set to true when the ACME server does not support it it will create an error on the Order. Defaults to false. + type: boolean + externalAccountBinding: + description: ExternalAccountBinding is a reference to a CA external account of the ACME server. If set, upon registration cert-manager will attempt to associate the given external account credentials with the registered ACME account. + type: object + required: + - keyID + - keySecretRef + properties: + keyAlgorithm: + description: 'Deprecated: keyAlgorithm field exists for historical compatibility reasons and should not be used. The algorithm is now hardcoded to HS256 in golang/x/crypto/acme.' + type: string + enum: + - HS256 + - HS384 + - HS512 + keyID: + description: keyID is the ID of the CA key that the External Account is bound to. + type: string + keySecretRef: + description: keySecretRef is a Secret Key Selector referencing a data item in a Kubernetes Secret which holds the symmetric MAC key of the External Account Binding. The `key` is the index string that is paired with the key data in the Secret and should not be confused with the key data itself, or indeed with the External Account Binding keyID above. The secret key stored in the Secret **must** be un-padded, base64 URL encoded data. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + preferredChain: + description: 'PreferredChain is the chain to use if the ACME server outputs multiple. PreferredChain is no guarantee that this one gets delivered by the ACME endpoint. For example, for Let''s Encrypt''s DST crosssign you would use: "DST Root CA X3" or "ISRG Root X1" for the newer Let''s Encrypt root CA. This value picks the first certificate bundle in the ACME alternative chains that has a certificate with this value as its issuer''s CN' + type: string + maxLength: 64 + privateKeySecretRef: + description: PrivateKey is the name of a Kubernetes Secret resource that will be used to store the automatically generated ACME account private key. Optionally, a `key` may be specified to select a specific entry within the named Secret resource. If `key` is not specified, a default of `tls.key` will be used. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + server: + description: 'Server is the URL used to access the ACME server''s ''directory'' endpoint. For example, for Let''s Encrypt''s staging endpoint, you would use: "https://acme-staging-v02.api.letsencrypt.org/directory". Only ACME v2 endpoints (i.e. RFC 8555) are supported.' + type: string + skipTLSVerify: + description: Enables or disables validation of the ACME server TLS certificate. If true, requests to the ACME server will not have their TLS certificate validated (i.e. insecure connections will be allowed). Only enable this option in development environments. The cert-manager system installed roots will be used to verify connections to the ACME server if this is false. Defaults to false. + type: boolean + solvers: + description: 'Solvers is a list of challenge solvers that will be used to solve ACME challenges for the matching domains. Solver configurations must be provided in order to obtain certificates from an ACME server. For more information, see: https://cert-manager.io/docs/configuration/acme/' + type: array + items: + description: An ACMEChallengeSolver describes how to solve ACME challenges for the issuer it is part of. A selector may be provided to use different solving strategies for different DNS names. Only one of HTTP01 or DNS01 must be provided. + type: object + properties: + dns01: + description: Configures cert-manager to attempt to complete authorizations by performing the DNS01 challenge flow. + type: object + properties: + acmeDNS: + description: Use the 'ACME DNS' (https://github.com/joohoi/acme-dns) API to manage DNS01 challenge records. + type: object + required: + - accountSecretRef + - host + properties: + accountSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + host: + type: string + akamai: + description: Use the Akamai DNS zone management API to manage DNS01 challenge records. + type: object + required: + - accessTokenSecretRef + - clientSecretSecretRef + - clientTokenSecretRef + - serviceConsumerDomain + properties: + accessTokenSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + clientSecretSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + clientTokenSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + serviceConsumerDomain: + type: string + azureDNS: + description: Use the Microsoft Azure DNS API to manage DNS01 challenge records. + type: object + required: + - resourceGroupName + - subscriptionID + properties: + clientID: + description: if both this and ClientSecret are left unset MSI will be used + type: string + clientSecretSecretRef: + description: if both this and ClientID are left unset MSI will be used + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + environment: + description: name of the Azure environment (default AzurePublicCloud) + type: string + enum: + - AzurePublicCloud + - AzureChinaCloud + - AzureGermanCloud + - AzureUSGovernmentCloud + hostedZoneName: + description: name of the DNS zone that should be used + type: string + managedIdentity: + description: managed identity configuration, can not be used at the same time as clientID, clientSecretSecretRef or tenantID + type: object + properties: + clientID: + description: client ID of the managed identity, can not be used at the same time as resourceID + type: string + resourceID: + description: resource ID of the managed identity, can not be used at the same time as clientID + type: string + resourceGroupName: + description: resource group the DNS zone is located in + type: string + subscriptionID: + description: ID of the Azure subscription + type: string + tenantID: + description: when specifying ClientID and ClientSecret then this field is also needed + type: string + cloudDNS: + description: Use the Google Cloud DNS API to manage DNS01 challenge records. + type: object + required: + - project + properties: + hostedZoneName: + description: HostedZoneName is an optional field that tells cert-manager in which Cloud DNS zone the challenge record has to be created. If left empty cert-manager will automatically choose a zone. + type: string + project: + type: string + serviceAccountSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + cloudflare: + description: Use the Cloudflare API to manage DNS01 challenge records. + type: object + properties: + apiKeySecretRef: + description: 'API key to use to authenticate with Cloudflare. Note: using an API token to authenticate is now the recommended method as it allows greater control of permissions.' + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + apiTokenSecretRef: + description: API token used to authenticate with Cloudflare. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + email: + description: Email of the account, only required when using API key based authentication. + type: string + cnameStrategy: + description: CNAMEStrategy configures how the DNS01 provider should handle CNAME records when found in DNS zones. + type: string + enum: + - None + - Follow + digitalocean: + description: Use the DigitalOcean DNS API to manage DNS01 challenge records. + type: object + required: + - tokenSecretRef + properties: + tokenSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + rfc2136: + description: Use RFC2136 ("Dynamic Updates in the Domain Name System") (https://datatracker.ietf.org/doc/rfc2136/) to manage DNS01 challenge records. + type: object + required: + - nameserver + properties: + nameserver: + description: The IP address or hostname of an authoritative DNS server supporting RFC2136 in the form host:port. If the host is an IPv6 address it must be enclosed in square brackets (e.g [2001:db8::1]) ; port is optional. This field is required. + type: string + tsigAlgorithm: + description: 'The TSIG Algorithm configured in the DNS supporting RFC2136. Used only when ``tsigSecretSecretRef`` and ``tsigKeyName`` are defined. Supported values are (case-insensitive): ``HMACMD5`` (default), ``HMACSHA1``, ``HMACSHA256`` or ``HMACSHA512``.' + type: string + tsigKeyName: + description: The TSIG Key name configured in the DNS. If ``tsigSecretSecretRef`` is defined, this field is required. + type: string + tsigSecretSecretRef: + description: The name of the secret containing the TSIG value. If ``tsigKeyName`` is defined, this field is required. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + route53: + description: Use the AWS Route53 API to manage DNS01 challenge records. + type: object + required: + - region + properties: + accessKeyID: + description: 'The AccessKeyID is used for authentication. Cannot be set when SecretAccessKeyID is set. If neither the Access Key nor Key ID are set, we fall-back to using env vars, shared credentials file or AWS Instance metadata, see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials' + type: string + accessKeyIDSecretRef: + description: 'The SecretAccessKey is used for authentication. If set, pull the AWS access key ID from a key within a Kubernetes Secret. Cannot be set when AccessKeyID is set. If neither the Access Key nor Key ID are set, we fall-back to using env vars, shared credentials file or AWS Instance metadata, see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials' + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + hostedZoneID: + description: If set, the provider will manage only this zone in Route53 and will not do an lookup using the route53:ListHostedZonesByName api call. + type: string + region: + description: Always set the region when using AccessKeyID and SecretAccessKey + type: string + role: + description: Role is a Role ARN which the Route53 provider will assume using either the explicit credentials AccessKeyID/SecretAccessKey or the inferred credentials from environment variables, shared credentials file or AWS Instance metadata + type: string + secretAccessKeySecretRef: + description: 'The SecretAccessKey is used for authentication. If neither the Access Key nor Key ID are set, we fall-back to using env vars, shared credentials file or AWS Instance metadata, see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials' + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + webhook: + description: Configure an external webhook based DNS01 challenge solver to manage DNS01 challenge records. + type: object + required: + - groupName + - solverName + properties: + config: + description: Additional configuration that should be passed to the webhook apiserver when challenges are processed. This can contain arbitrary JSON data. Secret values should not be specified in this stanza. If secret values are needed (e.g. credentials for a DNS service), you should use a SecretKeySelector to reference a Secret resource. For details on the schema of this field, consult the webhook provider implementation's documentation. + x-kubernetes-preserve-unknown-fields: true + groupName: + description: The API group name that should be used when POSTing ChallengePayload resources to the webhook apiserver. This should be the same as the GroupName specified in the webhook provider implementation. + type: string + solverName: + description: The name of the solver to use, as defined in the webhook provider implementation. This will typically be the name of the provider, e.g. 'cloudflare'. + type: string + http01: + description: Configures cert-manager to attempt to complete authorizations by performing the HTTP01 challenge flow. It is not possible to obtain certificates for wildcard domain names (e.g. `*.example.com`) using the HTTP01 challenge mechanism. + type: object + properties: + gatewayHTTPRoute: + description: The Gateway API is a sig-network community API that models service networking in Kubernetes (https://gateway-api.sigs.k8s.io/). The Gateway solver will create HTTPRoutes with the specified labels in the same namespace as the challenge. This solver is experimental, and fields / behaviour may change in the future. + type: object + properties: + labels: + description: Custom labels that will be applied to HTTPRoutes created by cert-manager while solving HTTP-01 challenges. + type: object + additionalProperties: + type: string + parentRefs: + description: 'When solving an HTTP-01 challenge, cert-manager creates an HTTPRoute. cert-manager needs to know which parentRefs should be used when creating the HTTPRoute. Usually, the parentRef references a Gateway. See: https://gateway-api.sigs.k8s.io/v1alpha2/api-types/httproute/#attaching-to-gateways' + type: array + items: + description: "ParentRef identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid. \n References to objects with invalid Group and Kind are not valid, and must be rejected by the implementation, with appropriate Conditions set on the containing object." + type: object + required: + - name + properties: + group: + description: "Group is the group of the referent. \n Support: Core" + type: string + default: gateway.networking.k8s.io + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + kind: + description: "Kind is kind of the referent. \n Support: Core (Gateway) Support: Custom (Other Resources)" + type: string + default: Gateway + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + name: + description: "Name is the name of the referent. \n Support: Core" + type: string + maxLength: 253 + minLength: 1 + namespace: + description: "Namespace is the namespace of the referent. When unspecified (or empty string), this refers to the local namespace of the Route. \n Support: Core" + type: string + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + type: string + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + serviceType: + description: Optional service type for Kubernetes solver service. Supported values are NodePort or ClusterIP. If unset, defaults to NodePort. + type: string + ingress: + description: The ingress based HTTP01 challenge solver will solve challenges by creating or modifying Ingress resources in order to route requests for '/.well-known/acme-challenge/XYZ' to 'challenge solver' pods that are provisioned by cert-manager for each Challenge to be completed. + type: object + properties: + class: + description: The ingress class to use when creating Ingress resources to solve ACME challenges that use this challenge solver. Only one of 'class' or 'name' may be specified. + type: string + ingressTemplate: + description: Optional ingress template used to configure the ACME challenge solver ingress used for HTTP01 challenges. + type: object + properties: + metadata: + description: ObjectMeta overrides for the ingress used to solve HTTP01 challenges. Only the 'labels' and 'annotations' fields may be set. If labels or annotations overlap with in-built values, the values here will override the in-built values. + type: object + properties: + annotations: + description: Annotations that should be added to the created ACME HTTP01 solver ingress. + type: object + additionalProperties: + type: string + labels: + description: Labels that should be added to the created ACME HTTP01 solver ingress. + type: object + additionalProperties: + type: string + name: + description: The name of the ingress resource that should have ACME challenge solving routes inserted into it in order to solve HTTP01 challenges. This is typically used in conjunction with ingress controllers like ingress-gce, which maintains a 1:1 mapping between external IPs and ingress resources. + type: string + podTemplate: + description: Optional pod template used to configure the ACME challenge solver pods used for HTTP01 challenges. + type: object + properties: + metadata: + description: ObjectMeta overrides for the pod used to solve HTTP01 challenges. Only the 'labels' and 'annotations' fields may be set. If labels or annotations overlap with in-built values, the values here will override the in-built values. + type: object + properties: + annotations: + description: Annotations that should be added to the create ACME HTTP01 solver pods. + type: object + additionalProperties: + type: string + labels: + description: Labels that should be added to the created ACME HTTP01 solver pods. + type: object + additionalProperties: + type: string + spec: + description: PodSpec defines overrides for the HTTP01 challenge solver pod. Only the 'priorityClassName', 'nodeSelector', 'affinity', 'serviceAccountName' and 'tolerations' fields are supported currently. All other fields will be ignored. + type: object + properties: + affinity: + description: If specified, the pod's scheduling constraints + type: object + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for the pod. + type: object + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node matches the corresponding matchExpressions; the node(s) with the highest sum are the most preferred. + type: array + items: + description: An empty preferred scheduling term matches all objects with implicit weight 0 (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + type: object + required: + - preference + - weight + properties: + preference: + description: A node selector term, associated with the corresponding weight. + type: object + properties: + matchExpressions: + description: A list of node selector requirements by node's labels. + type: array + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchFields: + description: A list of node selector requirements by node's fields. + type: array + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + type: array + items: + type: string + weight: + description: Weight associated with matching the corresponding nodeSelectorTerm, in the range 1-100. + type: integer + format: int32 + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to an update), the system may or may not try to eventually evict the pod from its node. + type: object + required: + - nodeSelectorTerms + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. The terms are ORed. + type: array + items: + description: A null or empty node selector term matches no objects. The requirements of them are ANDed. The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + type: object + properties: + matchExpressions: + description: A list of node selector requirements by node's labels. + type: array + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchFields: + description: A list of node selector requirements by node's fields. + type: array + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + type: array + items: + type: string + podAffinity: + description: Describes pod affinity scheduling rules (e.g. co-locate this pod in the same node, zone, etc. as some other pod(s)). + type: object + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. + type: array + items: + description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) + type: object + required: + - podAffinityTerm + - weight + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated with the corresponding weight. + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + weight: + description: weight associated with matching the corresponding podAffinityTerm, in the range 1-100. + type: integer + format: int32 + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied. + type: array + items: + description: Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key matches that of any node on which a pod of the set of pods is running + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod in the same node, zone, etc. as some other pod(s)). + type: object + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to nodes that satisfy the anti-affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. + type: array + items: + description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) + type: object + required: + - podAffinityTerm + - weight + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated with the corresponding weight. + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + weight: + description: weight associated with matching the corresponding podAffinityTerm, in the range 1-100. + type: integer + format: int32 + requiredDuringSchedulingIgnoredDuringExecution: + description: If the anti-affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the anti-affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied. + type: array + items: + description: Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key matches that of any node on which a pod of the set of pods is running + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + nodeSelector: + description: 'NodeSelector is a selector which must be true for the pod to fit on a node. Selector which must match a node''s labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' + type: object + additionalProperties: + type: string + priorityClassName: + description: If specified, the pod's priorityClassName. + type: string + serviceAccountName: + description: If specified, the pod's service account + type: string + tolerations: + description: If specified, the pod's tolerations. + type: array + items: + description: The pod this Toleration is attached to tolerates any taint that matches the triple using the matching operator . + type: object + properties: + effect: + description: Effect indicates the taint effect to match. Empty means match all taint effects. When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies to. Empty means match all taint keys. If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: Operator represents a key's relationship to the value. Valid operators are Exists and Equal. Defaults to Equal. Exists is equivalent to wildcard for value, so that a pod can tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of time the toleration (which must be of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, it is not set, which means tolerate the taint forever (do not evict). Zero and negative values will be treated as 0 (evict immediately) by the system. + type: integer + format: int64 + value: + description: Value is the taint value the toleration matches to. If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + serviceType: + description: Optional service type for Kubernetes solver service. Supported values are NodePort or ClusterIP. If unset, defaults to NodePort. + type: string + selector: + description: Selector selects a set of DNSNames on the Certificate resource that should be solved using this challenge solver. If not specified, the solver will be treated as the 'default' solver with the lowest priority, i.e. if any other solver has a more specific match, it will be used instead. + type: object + properties: + dnsNames: + description: List of DNSNames that this solver will be used to solve. If specified and a match is found, a dnsNames selector will take precedence over a dnsZones selector. If multiple solvers match with the same dnsNames value, the solver with the most matching labels in matchLabels will be selected. If neither has more matches, the solver defined earlier in the list will be selected. + type: array + items: + type: string + dnsZones: + description: List of DNSZones that this solver will be used to solve. The most specific DNS zone match specified here will take precedence over other DNS zone matches, so a solver specifying sys.example.com will be selected over one specifying example.com for the domain www.sys.example.com. If multiple solvers match with the same dnsZones value, the solver with the most matching labels in matchLabels will be selected. If neither has more matches, the solver defined earlier in the list will be selected. + type: array + items: + type: string + matchLabels: + description: A label selector that is used to refine the set of certificate's that this challenge solver will apply to. + type: object + additionalProperties: + type: string + ca: + description: CA configures this issuer to sign certificates using a signing CA keypair stored in a Secret resource. This is used to build internal PKIs that are managed by cert-manager. + type: object + required: + - secretName + properties: + crlDistributionPoints: + description: The CRL distribution points is an X.509 v3 certificate extension which identifies the location of the CRL from which the revocation of this certificate can be checked. If not set, certificates will be issued without distribution points set. + type: array + items: + type: string + ocspServers: + description: The OCSP server list is an X.509 v3 extension that defines a list of URLs of OCSP responders. The OCSP responders can be queried for the revocation status of an issued certificate. If not set, the certificate will be issued with no OCSP servers set. For example, an OCSP server URL could be "http://ocsp.int-x3.letsencrypt.org". + type: array + items: + type: string + secretName: + description: SecretName is the name of the secret used to sign Certificates issued by this Issuer. + type: string + selfSigned: + description: SelfSigned configures this issuer to 'self sign' certificates using the private key used to create the CertificateRequest object. + type: object + properties: + crlDistributionPoints: + description: The CRL distribution points is an X.509 v3 certificate extension which identifies the location of the CRL from which the revocation of this certificate can be checked. If not set certificate will be issued without CDP. Values are strings. + type: array + items: + type: string + vault: + description: Vault configures this issuer to sign certificates using a HashiCorp Vault PKI backend. + type: object + required: + - auth + - path + - server + properties: + auth: + description: Auth configures how cert-manager authenticates with the Vault server. + type: object + properties: + appRole: + description: AppRole authenticates with Vault using the App Role auth mechanism, with the role and secret stored in a Kubernetes Secret resource. + type: object + required: + - path + - roleId + - secretRef + properties: + path: + description: 'Path where the App Role authentication backend is mounted in Vault, e.g: "approle"' + type: string + roleId: + description: RoleID configured in the App Role authentication backend when setting up the authentication backend in Vault. + type: string + secretRef: + description: Reference to a key in a Secret that contains the App Role secret used to authenticate with Vault. The `key` field must be specified and denotes which entry within the Secret resource is used as the app role secret. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + kubernetes: + description: Kubernetes authenticates with Vault by passing the ServiceAccount token stored in the named Secret resource to the Vault server. + type: object + required: + - role + - secretRef + properties: + mountPath: + description: The Vault mountPath here is the mount path to use when authenticating with Vault. For example, setting a value to `/v1/auth/foo`, will use the path `/v1/auth/foo/login` to authenticate with Vault. If unspecified, the default value "/v1/auth/kubernetes" will be used. + type: string + role: + description: A required field containing the Vault Role to assume. A Role binds a Kubernetes ServiceAccount with a set of Vault policies. + type: string + secretRef: + description: The required Secret field containing a Kubernetes ServiceAccount JWT used for authenticating with Vault. Use of 'ambient credentials' is not supported. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + tokenSecretRef: + description: TokenSecretRef authenticates with Vault by presenting a token. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + caBundle: + description: PEM-encoded CA bundle (base64-encoded) used to validate Vault server certificate. Only used if the Server URL is using HTTPS protocol. This parameter is ignored for plain HTTP protocol connection. If not set the system root certificates are used to validate the TLS connection. + type: string + format: byte + namespace: + description: 'Name of the vault namespace. Namespaces is a set of features within Vault Enterprise that allows Vault environments to support Secure Multi-tenancy. e.g: "ns1" More about namespaces can be found here https://www.vaultproject.io/docs/enterprise/namespaces' + type: string + path: + description: 'Path is the mount path of the Vault PKI backend''s `sign` endpoint, e.g: "my_pki_mount/sign/my-role-name".' + type: string + server: + description: 'Server is the connection address for the Vault server, e.g: "https://vault.example.com:8200".' + type: string + venafi: + description: Venafi configures this issuer to sign certificates using a Venafi TPP or Venafi Cloud policy zone. + type: object + required: + - zone + properties: + cloud: + description: Cloud specifies the Venafi cloud configuration settings. Only one of TPP or Cloud may be specified. + type: object + required: + - apiTokenSecretRef + properties: + apiTokenSecretRef: + description: APITokenSecretRef is a secret key selector for the Venafi Cloud API token. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + url: + description: URL is the base URL for Venafi Cloud. Defaults to "https://api.venafi.cloud/v1". + type: string + tpp: + description: TPP specifies Trust Protection Platform configuration settings. Only one of TPP or Cloud may be specified. + type: object + required: + - credentialsRef + - url + properties: + caBundle: + description: CABundle is a PEM encoded TLS certificate to use to verify connections to the TPP instance. If specified, system roots will not be used and the issuing CA for the TPP instance must be verifiable using the provided root. If not specified, the connection will be verified using the cert-manager system root certificates. + type: string + format: byte + credentialsRef: + description: CredentialsRef is a reference to a Secret containing the username and password for the TPP server. The secret must contain two keys, 'username' and 'password'. + type: object + required: + - name + properties: + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + url: + description: 'URL is the base URL for the vedsdk endpoint of the Venafi TPP instance, for example: "https://tpp.example.com/vedsdk".' + type: string + zone: + description: Zone is the Venafi Policy Zone to use for this issuer. All requests made to the Venafi platform will be restricted by the named zone policy. This field is required. + type: string + status: + description: Status of the Issuer. This is set and managed automatically. + type: object + properties: + acme: + description: ACME specific status options. This field should only be set if the Issuer is configured to use an ACME server to issue certificates. + type: object + properties: + lastRegisteredEmail: + description: LastRegisteredEmail is the email associated with the latest registered ACME account, in order to track changes made to registered account associated with the Issuer + type: string + uri: + description: URI is the unique account identifier, which can also be used to retrieve account details from the CA + type: string + conditions: + description: List of status conditions to indicate the status of a CertificateRequest. Known condition types are `Ready`. + type: array + items: + description: IssuerCondition contains condition information for an Issuer. + type: object + required: + - status + - type + properties: + lastTransitionTime: + description: LastTransitionTime is the timestamp corresponding to the last status change of this condition. + type: string + format: date-time + message: + description: Message is a human readable description of the details of the last transition, complementing reason. + type: string + observedGeneration: + description: If set, this represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.condition[x].observedGeneration is 9, the condition is out of date with respect to the current state of the Issuer. + type: integer + format: int64 + reason: + description: Reason is a brief machine readable explanation for the condition's last transition. + type: string + status: + description: Status of the condition, one of (`True`, `False`, `Unknown`). + type: string + enum: + - "True" + - "False" + - Unknown + type: + description: Type of the condition, known values are (`Ready`). + type: string + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + served: true + storage: true +--- +# Source: cert-manager/templates/crd-templates.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: orders.acme.cert-manager.io + labels: + app: 'cert-manager' + app.kubernetes.io/name: 'cert-manager' + app.kubernetes.io/instance: 'cert-manager' + # Generated labels + app.kubernetes.io/version: "v1.9.1" +spec: + group: acme.cert-manager.io + names: + kind: Order + listKind: OrderList + plural: orders + singular: order + categories: + - cert-manager + - cert-manager-acme + scope: Namespaced + versions: + - name: v1 + subresources: + status: {} + additionalPrinterColumns: + - jsonPath: .status.state + name: State + type: string + - jsonPath: .spec.issuerRef.name + name: Issuer + priority: 1 + type: string + - jsonPath: .status.reason + name: Reason + priority: 1 + type: string + - jsonPath: .metadata.creationTimestamp + description: CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC. + name: Age + type: date + schema: + openAPIV3Schema: + description: Order is a type to represent an Order with an ACME server + type: object + required: + - metadata + - spec + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + type: object + required: + - issuerRef + - request + properties: + commonName: + description: CommonName is the common name as specified on the DER encoded CSR. If specified, this value must also be present in `dnsNames` or `ipAddresses`. This field must match the corresponding field on the DER encoded CSR. + type: string + dnsNames: + description: DNSNames is a list of DNS names that should be included as part of the Order validation process. This field must match the corresponding field on the DER encoded CSR. + type: array + items: + type: string + duration: + description: Duration is the duration for the not after date for the requested certificate. this is set on order creation as pe the ACME spec. + type: string + ipAddresses: + description: IPAddresses is a list of IP addresses that should be included as part of the Order validation process. This field must match the corresponding field on the DER encoded CSR. + type: array + items: + type: string + issuerRef: + description: IssuerRef references a properly configured ACME-type Issuer which should be used to create this Order. If the Issuer does not exist, processing will be retried. If the Issuer is not an 'ACME' Issuer, an error will be returned and the Order will be marked as failed. + type: object + required: + - name + properties: + group: + description: Group of the resource being referred to. + type: string + kind: + description: Kind of the resource being referred to. + type: string + name: + description: Name of the resource being referred to. + type: string + request: + description: Certificate signing request bytes in DER encoding. This will be used when finalizing the order. This field must be set on the order. + type: string + format: byte + status: + type: object + properties: + authorizations: + description: Authorizations contains data returned from the ACME server on what authorizations must be completed in order to validate the DNS names specified on the Order. + type: array + items: + description: ACMEAuthorization contains data returned from the ACME server on an authorization that must be completed in order validate a DNS name on an ACME Order resource. + type: object + required: + - url + properties: + challenges: + description: Challenges specifies the challenge types offered by the ACME server. One of these challenge types will be selected when validating the DNS name and an appropriate Challenge resource will be created to perform the ACME challenge process. + type: array + items: + description: Challenge specifies a challenge offered by the ACME server for an Order. An appropriate Challenge resource can be created to perform the ACME challenge process. + type: object + required: + - token + - type + - url + properties: + token: + description: Token is the token that must be presented for this challenge. This is used to compute the 'key' that must also be presented. + type: string + type: + description: Type is the type of challenge being offered, e.g. 'http-01', 'dns-01', 'tls-sni-01', etc. This is the raw value retrieved from the ACME server. Only 'http-01' and 'dns-01' are supported by cert-manager, other values will be ignored. + type: string + url: + description: URL is the URL of this challenge. It can be used to retrieve additional metadata about the Challenge from the ACME server. + type: string + identifier: + description: Identifier is the DNS name to be validated as part of this authorization + type: string + initialState: + description: InitialState is the initial state of the ACME authorization when first fetched from the ACME server. If an Authorization is already 'valid', the Order controller will not create a Challenge resource for the authorization. This will occur when working with an ACME server that enables 'authz reuse' (such as Let's Encrypt's production endpoint). If not set and 'identifier' is set, the state is assumed to be pending and a Challenge will be created. + type: string + enum: + - valid + - ready + - pending + - processing + - invalid + - expired + - errored + url: + description: URL is the URL of the Authorization that must be completed + type: string + wildcard: + description: Wildcard will be true if this authorization is for a wildcard DNS name. If this is true, the identifier will be the *non-wildcard* version of the DNS name. For example, if '*.example.com' is the DNS name being validated, this field will be 'true' and the 'identifier' field will be 'example.com'. + type: boolean + certificate: + description: Certificate is a copy of the PEM encoded certificate for this Order. This field will be populated after the order has been successfully finalized with the ACME server, and the order has transitioned to the 'valid' state. + type: string + format: byte + failureTime: + description: FailureTime stores the time that this order failed. This is used to influence garbage collection and back-off. + type: string + format: date-time + finalizeURL: + description: FinalizeURL of the Order. This is used to obtain certificates for this order once it has been completed. + type: string + reason: + description: Reason optionally provides more information about a why the order is in the current state. + type: string + state: + description: State contains the current state of this Order resource. States 'success' and 'expired' are 'final' + type: string + enum: + - valid + - ready + - pending + - processing + - invalid + - expired + - errored + url: + description: URL of the Order. This will initially be empty when the resource is first created. The Order controller will populate this field when the Order is first processed. This field will be immutable after it is initially set. + type: string + served: true + storage: true +--- +# Source: cert-manager/templates/cainjector-serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +automountServiceAccountToken: true +metadata: + name: cert-manager-cainjector + namespace: cert-manager + labels: + app: cainjector + app.kubernetes.io/name: cainjector + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "cainjector" + app.kubernetes.io/version: "v1.9.1" +--- +# Source: cert-manager/templates/serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +automountServiceAccountToken: true +metadata: + name: cert-manager + namespace: cert-manager + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" + app.kubernetes.io/version: "v1.9.1" +--- +# Source: cert-manager/templates/webhook-serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +automountServiceAccountToken: true +metadata: + name: cert-manager-webhook + namespace: cert-manager + labels: + app: webhook + app.kubernetes.io/name: webhook + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "webhook" + app.kubernetes.io/version: "v1.9.1" +--- +# Source: cert-manager/templates/webhook-config.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: cert-manager-webhook + namespace: cert-manager + labels: + app: webhook + app.kubernetes.io/name: webhook + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "webhook" +data: +--- +# Source: cert-manager/templates/cainjector-rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cert-manager-cainjector + labels: + app: cainjector + app.kubernetes.io/name: cainjector + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "cainjector" + app.kubernetes.io/version: "v1.9.1" +rules: + - apiGroups: ["cert-manager.io"] + resources: ["certificates"] + verbs: ["get", "list", "watch"] + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "list", "watch"] + - apiGroups: [""] + resources: ["events"] + verbs: ["get", "create", "update", "patch"] + - apiGroups: ["admissionregistration.k8s.io"] + resources: ["validatingwebhookconfigurations", "mutatingwebhookconfigurations"] + verbs: ["get", "list", "watch", "update"] + - apiGroups: ["apiregistration.k8s.io"] + resources: ["apiservices"] + verbs: ["get", "list", "watch", "update"] + - apiGroups: ["apiextensions.k8s.io"] + resources: ["customresourcedefinitions"] + verbs: ["get", "list", "watch", "update"] +--- +# Source: cert-manager/templates/rbac.yaml +# Issuer controller role +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cert-manager-controller-issuers + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" + app.kubernetes.io/version: "v1.9.1" +rules: + - apiGroups: ["cert-manager.io"] + resources: ["issuers", "issuers/status"] + verbs: ["update", "patch"] + - apiGroups: ["cert-manager.io"] + resources: ["issuers"] + verbs: ["get", "list", "watch"] + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "list", "watch", "create", "update", "delete"] + - apiGroups: [""] + resources: ["events"] + verbs: ["create", "patch"] +--- +# Source: cert-manager/templates/rbac.yaml +# ClusterIssuer controller role +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cert-manager-controller-clusterissuers + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" + app.kubernetes.io/version: "v1.9.1" +rules: + - apiGroups: ["cert-manager.io"] + resources: ["clusterissuers", "clusterissuers/status"] + verbs: ["update", "patch"] + - apiGroups: ["cert-manager.io"] + resources: ["clusterissuers"] + verbs: ["get", "list", "watch"] + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "list", "watch", "create", "update", "delete"] + - apiGroups: [""] + resources: ["events"] + verbs: ["create", "patch"] +--- +# Source: cert-manager/templates/rbac.yaml +# Certificates controller role +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cert-manager-controller-certificates + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" + app.kubernetes.io/version: "v1.9.1" +rules: + - apiGroups: ["cert-manager.io"] + resources: ["certificates", "certificates/status", "certificaterequests", "certificaterequests/status"] + verbs: ["update", "patch"] + - apiGroups: ["cert-manager.io"] + resources: ["certificates", "certificaterequests", "clusterissuers", "issuers"] + verbs: ["get", "list", "watch"] + # We require these rules to support users with the OwnerReferencesPermissionEnforcement + # admission controller enabled: + # https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#ownerreferencespermissionenforcement + - apiGroups: ["cert-manager.io"] + resources: ["certificates/finalizers", "certificaterequests/finalizers"] + verbs: ["update"] + - apiGroups: ["acme.cert-manager.io"] + resources: ["orders"] + verbs: ["create", "delete", "get", "list", "watch"] + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "list", "watch", "create", "update", "delete", "patch"] + - apiGroups: [""] + resources: ["events"] + verbs: ["create", "patch"] +--- +# Source: cert-manager/templates/rbac.yaml +# Orders controller role +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cert-manager-controller-orders + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" + app.kubernetes.io/version: "v1.9.1" +rules: + - apiGroups: ["acme.cert-manager.io"] + resources: ["orders", "orders/status"] + verbs: ["update", "patch"] + - apiGroups: ["acme.cert-manager.io"] + resources: ["orders", "challenges"] + verbs: ["get", "list", "watch"] + - apiGroups: ["cert-manager.io"] + resources: ["clusterissuers", "issuers"] + verbs: ["get", "list", "watch"] + - apiGroups: ["acme.cert-manager.io"] + resources: ["challenges"] + verbs: ["create", "delete"] + # We require these rules to support users with the OwnerReferencesPermissionEnforcement + # admission controller enabled: + # https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#ownerreferencespermissionenforcement + - apiGroups: ["acme.cert-manager.io"] + resources: ["orders/finalizers"] + verbs: ["update"] + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "list", "watch"] + - apiGroups: [""] + resources: ["events"] + verbs: ["create", "patch"] +--- +# Source: cert-manager/templates/rbac.yaml +# Challenges controller role +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cert-manager-controller-challenges + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" + app.kubernetes.io/version: "v1.9.1" +rules: + # Use to update challenge resource status + - apiGroups: ["acme.cert-manager.io"] + resources: ["challenges", "challenges/status"] + verbs: ["update", "patch"] + # Used to watch challenge resources + - apiGroups: ["acme.cert-manager.io"] + resources: ["challenges"] + verbs: ["get", "list", "watch"] + # Used to watch challenges, issuer and clusterissuer resources + - apiGroups: ["cert-manager.io"] + resources: ["issuers", "clusterissuers"] + verbs: ["get", "list", "watch"] + # Need to be able to retrieve ACME account private key to complete challenges + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "list", "watch"] + # Used to create events + - apiGroups: [""] + resources: ["events"] + verbs: ["create", "patch"] + # HTTP01 rules + - apiGroups: [""] + resources: ["pods", "services"] + verbs: ["get", "list", "watch", "create", "delete"] + - apiGroups: ["networking.k8s.io"] + resources: ["ingresses"] + verbs: ["get", "list", "watch", "create", "delete", "update"] + - apiGroups: [ "gateway.networking.k8s.io" ] + resources: [ "httproutes" ] + verbs: ["get", "list", "watch", "create", "delete", "update"] + # We require the ability to specify a custom hostname when we are creating + # new ingress resources. + # See: https://github.com/openshift/origin/blob/21f191775636f9acadb44fa42beeb4f75b255532/pkg/route/apiserver/admission/ingress_admission.go#L84-L148 + - apiGroups: ["route.openshift.io"] + resources: ["routes/custom-host"] + verbs: ["create"] + # We require these rules to support users with the OwnerReferencesPermissionEnforcement + # admission controller enabled: + # https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#ownerreferencespermissionenforcement + - apiGroups: ["acme.cert-manager.io"] + resources: ["challenges/finalizers"] + verbs: ["update"] + # DNS01 rules (duplicated above) + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "list", "watch"] +--- +# Source: cert-manager/templates/rbac.yaml +# ingress-shim controller role +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cert-manager-controller-ingress-shim + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" + app.kubernetes.io/version: "v1.9.1" +rules: + - apiGroups: ["cert-manager.io"] + resources: ["certificates", "certificaterequests"] + verbs: ["create", "update", "delete"] + - apiGroups: ["cert-manager.io"] + resources: ["certificates", "certificaterequests", "issuers", "clusterissuers"] + verbs: ["get", "list", "watch"] + - apiGroups: ["networking.k8s.io"] + resources: ["ingresses"] + verbs: ["get", "list", "watch"] + # We require these rules to support users with the OwnerReferencesPermissionEnforcement + # admission controller enabled: + # https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#ownerreferencespermissionenforcement + - apiGroups: ["networking.k8s.io"] + resources: ["ingresses/finalizers"] + verbs: ["update"] + - apiGroups: ["gateway.networking.k8s.io"] + resources: ["gateways", "httproutes"] + verbs: ["get", "list", "watch"] + - apiGroups: ["gateway.networking.k8s.io"] + resources: ["gateways/finalizers", "httproutes/finalizers"] + verbs: ["update"] + - apiGroups: [""] + resources: ["events"] + verbs: ["create", "patch"] +--- +# Source: cert-manager/templates/rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cert-manager-view + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" + app.kubernetes.io/version: "v1.9.1" + rbac.authorization.k8s.io/aggregate-to-view: "true" + rbac.authorization.k8s.io/aggregate-to-edit: "true" + rbac.authorization.k8s.io/aggregate-to-admin: "true" +rules: + - apiGroups: ["cert-manager.io"] + resources: ["certificates", "certificaterequests", "issuers"] + verbs: ["get", "list", "watch"] + - apiGroups: ["acme.cert-manager.io"] + resources: ["challenges", "orders"] + verbs: ["get", "list", "watch"] +--- +# Source: cert-manager/templates/rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cert-manager-edit + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" + app.kubernetes.io/version: "v1.9.1" + rbac.authorization.k8s.io/aggregate-to-edit: "true" + rbac.authorization.k8s.io/aggregate-to-admin: "true" +rules: + - apiGroups: ["cert-manager.io"] + resources: ["certificates", "certificaterequests", "issuers"] + verbs: ["create", "delete", "deletecollection", "patch", "update"] + - apiGroups: ["cert-manager.io"] + resources: ["certificates/status"] + verbs: ["update"] + - apiGroups: ["acme.cert-manager.io"] + resources: ["challenges", "orders"] + verbs: ["create", "delete", "deletecollection", "patch", "update"] +--- +# Source: cert-manager/templates/rbac.yaml +# Permission to approve CertificateRequests referencing cert-manager.io Issuers and ClusterIssuers +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cert-manager-controller-approve:cert-manager-io + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "cert-manager" + app.kubernetes.io/version: "v1.9.1" +rules: + - apiGroups: ["cert-manager.io"] + resources: ["signers"] + verbs: ["approve"] + resourceNames: ["issuers.cert-manager.io/*", "clusterissuers.cert-manager.io/*"] +--- +# Source: cert-manager/templates/rbac.yaml +# Permission to: +# - Update and sign CertificatSigningeRequests referencing cert-manager.io Issuers and ClusterIssuers +# - Perform SubjectAccessReviews to test whether users are able to reference Namespaced Issuers +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cert-manager-controller-certificatesigningrequests + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "cert-manager" + app.kubernetes.io/version: "v1.9.1" +rules: + - apiGroups: ["certificates.k8s.io"] + resources: ["certificatesigningrequests"] + verbs: ["get", "list", "watch", "update"] + - apiGroups: ["certificates.k8s.io"] + resources: ["certificatesigningrequests/status"] + verbs: ["update", "patch"] + - apiGroups: ["certificates.k8s.io"] + resources: ["signers"] + resourceNames: ["issuers.cert-manager.io/*", "clusterissuers.cert-manager.io/*"] + verbs: ["sign"] + - apiGroups: ["authorization.k8s.io"] + resources: ["subjectaccessreviews"] + verbs: ["create"] +--- +# Source: cert-manager/templates/webhook-rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cert-manager-webhook:subjectaccessreviews + labels: + app: webhook + app.kubernetes.io/name: webhook + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "webhook" + app.kubernetes.io/version: "v1.9.1" +rules: +- apiGroups: ["authorization.k8s.io"] + resources: ["subjectaccessreviews"] + verbs: ["create"] +--- +# Source: cert-manager/templates/cainjector-rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: cert-manager-cainjector + labels: + app: cainjector + app.kubernetes.io/name: cainjector + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "cainjector" + app.kubernetes.io/version: "v1.9.1" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cert-manager-cainjector +subjects: + - name: cert-manager-cainjector + namespace: cert-manager + kind: ServiceAccount +--- +# Source: cert-manager/templates/rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: cert-manager-controller-issuers + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" + app.kubernetes.io/version: "v1.9.1" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cert-manager-controller-issuers +subjects: + - name: cert-manager + namespace: cert-manager + kind: ServiceAccount +--- +# Source: cert-manager/templates/rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: cert-manager-controller-clusterissuers + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" + app.kubernetes.io/version: "v1.9.1" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cert-manager-controller-clusterissuers +subjects: + - name: cert-manager + namespace: cert-manager + kind: ServiceAccount +--- +# Source: cert-manager/templates/rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: cert-manager-controller-certificates + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" + app.kubernetes.io/version: "v1.9.1" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cert-manager-controller-certificates +subjects: + - name: cert-manager + namespace: cert-manager + kind: ServiceAccount +--- +# Source: cert-manager/templates/rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: cert-manager-controller-orders + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" + app.kubernetes.io/version: "v1.9.1" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cert-manager-controller-orders +subjects: + - name: cert-manager + namespace: cert-manager + kind: ServiceAccount +--- +# Source: cert-manager/templates/rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: cert-manager-controller-challenges + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" + app.kubernetes.io/version: "v1.9.1" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cert-manager-controller-challenges +subjects: + - name: cert-manager + namespace: cert-manager + kind: ServiceAccount +--- +# Source: cert-manager/templates/rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: cert-manager-controller-ingress-shim + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" + app.kubernetes.io/version: "v1.9.1" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cert-manager-controller-ingress-shim +subjects: + - name: cert-manager + namespace: cert-manager + kind: ServiceAccount +--- +# Source: cert-manager/templates/rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: cert-manager-controller-approve:cert-manager-io + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "cert-manager" + app.kubernetes.io/version: "v1.9.1" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cert-manager-controller-approve:cert-manager-io +subjects: + - name: cert-manager + namespace: cert-manager + kind: ServiceAccount +--- +# Source: cert-manager/templates/rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: cert-manager-controller-certificatesigningrequests + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "cert-manager" + app.kubernetes.io/version: "v1.9.1" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cert-manager-controller-certificatesigningrequests +subjects: + - name: cert-manager + namespace: cert-manager + kind: ServiceAccount +--- +# Source: cert-manager/templates/webhook-rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: cert-manager-webhook:subjectaccessreviews + labels: + app: webhook + app.kubernetes.io/name: webhook + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "webhook" + app.kubernetes.io/version: "v1.9.1" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cert-manager-webhook:subjectaccessreviews +subjects: +- apiGroup: "" + kind: ServiceAccount + name: cert-manager-webhook + namespace: cert-manager +--- +# Source: cert-manager/templates/cainjector-rbac.yaml +# leader election rules +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: cert-manager-cainjector:leaderelection + namespace: kube-system + labels: + app: cainjector + app.kubernetes.io/name: cainjector + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "cainjector" + app.kubernetes.io/version: "v1.9.1" +rules: + # Used for leader election by the controller + # cert-manager-cainjector-leader-election is used by the CertificateBased injector controller + # see cmd/cainjector/start.go#L113 + # cert-manager-cainjector-leader-election-core is used by the SecretBased injector controller + # see cmd/cainjector/start.go#L137 + - apiGroups: ["coordination.k8s.io"] + resources: ["leases"] + resourceNames: ["cert-manager-cainjector-leader-election", "cert-manager-cainjector-leader-election-core"] + verbs: ["get", "update", "patch"] + - apiGroups: ["coordination.k8s.io"] + resources: ["leases"] + verbs: ["create"] +--- +# Source: cert-manager/templates/rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: cert-manager:leaderelection + namespace: kube-system + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" + app.kubernetes.io/version: "v1.9.1" +rules: + - apiGroups: ["coordination.k8s.io"] + resources: ["leases"] + resourceNames: ["cert-manager-controller"] + verbs: ["get", "update", "patch"] + - apiGroups: ["coordination.k8s.io"] + resources: ["leases"] + verbs: ["create"] +--- +# Source: cert-manager/templates/webhook-rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: cert-manager-webhook:dynamic-serving + namespace: cert-manager + labels: + app: webhook + app.kubernetes.io/name: webhook + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "webhook" + app.kubernetes.io/version: "v1.9.1" +rules: +- apiGroups: [""] + resources: ["secrets"] + resourceNames: + - 'cert-manager-webhook-ca' + verbs: ["get", "list", "watch", "update"] +# It's not possible to grant CREATE permission on a single resourceName. +- apiGroups: [""] + resources: ["secrets"] + verbs: ["create"] +--- +# Source: cert-manager/templates/cainjector-rbac.yaml +# grant cert-manager permission to manage the leaderelection configmap in the +# leader election namespace +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: cert-manager-cainjector:leaderelection + namespace: kube-system + labels: + app: cainjector + app.kubernetes.io/name: cainjector + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "cainjector" + app.kubernetes.io/version: "v1.9.1" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: cert-manager-cainjector:leaderelection +subjects: + - kind: ServiceAccount + name: cert-manager-cainjector + namespace: cert-manager +--- +# Source: cert-manager/templates/rbac.yaml +# grant cert-manager permission to manage the leaderelection configmap in the +# leader election namespace +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: cert-manager:leaderelection + namespace: kube-system + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" + app.kubernetes.io/version: "v1.9.1" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: cert-manager:leaderelection +subjects: + - apiGroup: "" + kind: ServiceAccount + name: cert-manager + namespace: cert-manager +--- +# Source: cert-manager/templates/webhook-rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: cert-manager-webhook:dynamic-serving + namespace: cert-manager + labels: + app: webhook + app.kubernetes.io/name: webhook + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "webhook" + app.kubernetes.io/version: "v1.9.1" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: cert-manager-webhook:dynamic-serving +subjects: +- apiGroup: "" + kind: ServiceAccount + name: cert-manager-webhook + namespace: cert-manager +--- +# Source: cert-manager/templates/service.yaml +apiVersion: v1 +kind: Service +metadata: + name: cert-manager + namespace: cert-manager + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" + app.kubernetes.io/version: "v1.9.1" +spec: + type: ClusterIP + ports: + - protocol: TCP + port: 9402 + name: tcp-prometheus-servicemonitor + targetPort: 9402 + selector: + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" +--- +# Source: cert-manager/templates/webhook-service.yaml +apiVersion: v1 +kind: Service +metadata: + name: cert-manager-webhook + namespace: cert-manager + labels: + app: webhook + app.kubernetes.io/name: webhook + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "webhook" + app.kubernetes.io/version: "v1.9.1" +spec: + type: ClusterIP + ports: + - name: https + port: 443 + protocol: TCP + targetPort: "https" + selector: + app.kubernetes.io/name: webhook + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "webhook" +--- +# Source: cert-manager/templates/cainjector-deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cert-manager-cainjector + namespace: cert-manager + labels: + app: cainjector + app.kubernetes.io/name: cainjector + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "cainjector" + app.kubernetes.io/version: "v1.9.1" +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: cainjector + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "cainjector" + template: + metadata: + labels: + app: cainjector + app.kubernetes.io/name: cainjector + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "cainjector" + app.kubernetes.io/version: "v1.9.1" + spec: + serviceAccountName: cert-manager-cainjector + securityContext: + runAsNonRoot: true + containers: + - name: cert-manager + image: "quay.io/jetstack/cert-manager-cainjector:v1.9.1" + imagePullPolicy: IfNotPresent + args: + - --v=2 + - --leader-election-namespace=kube-system + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + securityContext: + allowPrivilegeEscalation: false + nodeSelector: + kubernetes.io/os: linux +--- +# Source: cert-manager/templates/deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cert-manager + namespace: cert-manager + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" + app.kubernetes.io/version: "v1.9.1" +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" + template: + metadata: + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" + app.kubernetes.io/version: "v1.9.1" + annotations: + prometheus.io/path: "/metrics" + prometheus.io/scrape: 'true' + prometheus.io/port: '9402' + spec: + serviceAccountName: cert-manager + securityContext: + runAsNonRoot: true + containers: + - name: cert-manager + image: "quay.io/jetstack/cert-manager-controller:v1.9.1" + imagePullPolicy: IfNotPresent + args: + - --v=2 + - --cluster-resource-namespace=$(POD_NAMESPACE) + - --leader-election-namespace=kube-system + ports: + - containerPort: 9402 + name: http-metrics + protocol: TCP + securityContext: + allowPrivilegeEscalation: false + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + nodeSelector: + kubernetes.io/os: linux +--- +# Source: cert-manager/templates/webhook-deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cert-manager-webhook + namespace: cert-manager + labels: + app: webhook + app.kubernetes.io/name: webhook + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "webhook" + app.kubernetes.io/version: "v1.9.1" +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: webhook + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "webhook" + template: + metadata: + labels: + app: webhook + app.kubernetes.io/name: webhook + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "webhook" + app.kubernetes.io/version: "v1.9.1" + spec: + serviceAccountName: cert-manager-webhook + securityContext: + runAsNonRoot: true + containers: + - name: cert-manager + image: "quay.io/jetstack/cert-manager-webhook:v1.9.1" + imagePullPolicy: IfNotPresent + args: + - --v=2 + - --secure-port=10250 + - --dynamic-serving-ca-secret-namespace=$(POD_NAMESPACE) + - --dynamic-serving-ca-secret-name=cert-manager-webhook-ca + - --dynamic-serving-dns-names=cert-manager-webhook,cert-manager-webhook.cert-manager,cert-manager-webhook.cert-manager.svc + ports: + - name: https + protocol: TCP + containerPort: 10250 + livenessProbe: + httpGet: + path: /livez + port: 6080 + scheme: HTTP + initialDelaySeconds: 60 + periodSeconds: 10 + timeoutSeconds: 1 + successThreshold: 1 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /healthz + port: 6080 + scheme: HTTP + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 1 + successThreshold: 1 + failureThreshold: 3 + securityContext: + allowPrivilegeEscalation: false + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + nodeSelector: + kubernetes.io/os: linux +--- +# Source: cert-manager/templates/webhook-mutating-webhook.yaml +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: cert-manager-webhook + labels: + app: webhook + app.kubernetes.io/name: webhook + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "webhook" + app.kubernetes.io/version: "v1.9.1" + annotations: + cert-manager.io/inject-ca-from-secret: "cert-manager/cert-manager-webhook-ca" +webhooks: + - name: webhook.cert-manager.io + rules: + - apiGroups: + - "cert-manager.io" + - "acme.cert-manager.io" + apiVersions: + - "v1" + operations: + - CREATE + - UPDATE + resources: + - "*/*" + admissionReviewVersions: ["v1"] + # This webhook only accepts v1 cert-manager resources. + # Equivalent matchPolicy ensures that non-v1 resource requests are sent to + # this webhook (after the resources have been converted to v1). + matchPolicy: Equivalent + timeoutSeconds: 10 + failurePolicy: Fail + # Only include 'sideEffects' field in Kubernetes 1.12+ + sideEffects: None + clientConfig: + service: + name: cert-manager-webhook + namespace: cert-manager + path: /mutate +--- +# Source: cert-manager/templates/webhook-validating-webhook.yaml +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: cert-manager-webhook + labels: + app: webhook + app.kubernetes.io/name: webhook + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "webhook" + app.kubernetes.io/version: "v1.9.1" + annotations: + cert-manager.io/inject-ca-from-secret: "cert-manager/cert-manager-webhook-ca" +webhooks: + - name: webhook.cert-manager.io + namespaceSelector: + matchExpressions: + - key: "cert-manager.io/disable-validation" + operator: "NotIn" + values: + - "true" + - key: "name" + operator: "NotIn" + values: + - cert-manager + rules: + - apiGroups: + - "cert-manager.io" + - "acme.cert-manager.io" + apiVersions: + - "v1" + operations: + - CREATE + - UPDATE + resources: + - "*/*" + admissionReviewVersions: ["v1"] + # This webhook only accepts v1 cert-manager resources. + # Equivalent matchPolicy ensures that non-v1 resource requests are sent to + # this webhook (after the resources have been converted to v1). + matchPolicy: Equivalent + timeoutSeconds: 10 + failurePolicy: Fail + sideEffects: None + clientConfig: + service: + name: cert-manager-webhook + namespace: cert-manager + path: /validate diff --git a/ResearchOps/cert-manager/namespace.yaml b/ResearchOps/cert-manager/namespace.yaml new file mode 100644 index 0000000..6bc19f4 --- /dev/null +++ b/ResearchOps/cert-manager/namespace.yaml @@ -0,0 +1,5 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: cert-manager diff --git a/ResearchOps/harbor/.helmignore b/ResearchOps/harbor/.helmignore new file mode 100644 index 0000000..b4424fd --- /dev/null +++ b/ResearchOps/harbor/.helmignore @@ -0,0 +1,6 @@ +.github/* +docs/* +.git/* +.gitignore +CONTRIBUTING.md +test/* \ No newline at end of file diff --git a/ResearchOps/harbor/Chart.yaml b/ResearchOps/harbor/Chart.yaml new file mode 100644 index 0000000..371ae4e --- /dev/null +++ b/ResearchOps/harbor/Chart.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +appVersion: 2.5.3 +description: An open source trusted cloud native registry that stores, signs, and scans content +home: https://goharbor.io +icon: https://raw.githubusercontent.com/goharbor/website/master/static/img/logos/harbor-icon-color.png +keywords: +- docker +- registry +- harbor +maintainers: +- email: yinw@vmware.com + name: Wenkai Yin +- email: hweiwei@vmware.com + name: Weiwei He +- email: yshengwen@vmware.com + name: Shengwen Yu +name: harbor +sources: +- https://github.com/goharbor/harbor +- https://github.com/goharbor/harbor-helm +version: 1.9.3 diff --git a/ResearchOps/harbor/LICENSE b/ResearchOps/harbor/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/ResearchOps/harbor/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/ResearchOps/harbor/README.md b/ResearchOps/harbor/README.md new file mode 100644 index 0000000..1040f55 --- /dev/null +++ b/ResearchOps/harbor/README.md @@ -0,0 +1,420 @@ +# Helm Chart for Harbor + +**Notes:** The master branch is in heavy development, please use the other stable versions instead. A highly available solution for Harbor based on chart can be find [here](docs/High%20Availability.md). And refer to the [guide](docs/Upgrade.md) to upgrade the existing deployment. + +This repository, including the issues, focuses on deploying Harbor chart via helm. For functionality issues or Harbor questions, please open issues on [goharbor/harbor](https://github.com/goharbor/harbor) + +## Introduction + +This [Helm](https://github.com/kubernetes/helm) chart installs [Harbor](https://github.com/goharbor/harbor) in a Kubernetes cluster. Welcome to [contribute](CONTRIBUTING.md) to Helm Chart for Harbor. + +## Prerequisites + +- Kubernetes cluster 1.20+ +- Helm v3.2.0+ + +## Installation + +### Add Helm repository + +```bash +helm repo add harbor https://helm.goharbor.io +``` + +### Configure the chart + +The following items can be set via `--set` flag during installation or configured by editing the `values.yaml` directly (need to download the chart first). + +#### Configure how to expose Harbor service + +- **Ingress**: The ingress controller must be installed in the Kubernetes cluster. + **Notes:** if TLS is disabled, the port must be included in the command when pulling/pushing images. Refer to issue [#5291](https://github.com/goharbor/harbor/issues/5291) for details. +- **ClusterIP**: Exposes the service on a cluster-internal IP. Choosing this value makes the service only reachable from within the cluster. +- **NodePort**: Exposes the service on each Node’s IP at a static port (the NodePort). You’ll be able to contact the NodePort service, from outside the cluster, by requesting `NodeIP:NodePort`. +- **LoadBalancer**: Exposes the service externally using a cloud provider’s load balancer. + +#### Configure the external URL + +The external URL for Harbor core service is used to: + +1. populate the docker/helm commands showed on portal +2. populate the token service URL returned to docker/notary client + +Format: `protocol://domain[:port]`. Usually: + +- if service exposed via `Ingress`, the `domain` should be the value of `expose.ingress.hosts.core` +- if service exposed via `ClusterIP`, the `domain` should be the value of `expose.clusterIP.name` +- if service exposed via `NodePort`, the `domain` should be the IP address of one Kubernetes node +- if service exposed via `LoadBalancer`, set the `domain` as your own domain name and add a CNAME record to map the domain name to the one you got from the cloud provider + +If Harbor is deployed behind the proxy, set it as the URL of proxy. + +#### Configure how to persist data + +- **Disable**: The data does not survive the termination of a pod. +- **Persistent Volume Claim(default)**: A default `StorageClass` is needed in the Kubernetes cluster to dynamically provision the volumes. Specify another StorageClass in the `storageClass` or set `existingClaim` if you already have existing persistent volumes to use. +- **External Storage(only for images and charts)**: For images and charts, the external storages are supported: `azure`, `gcs`, `s3` `swift` and `oss`. + +#### Configure the other items listed in [configuration](#configuration) section + +### Install the chart + +Install the Harbor helm chart with a release name `my-release`: +```bash +helm install my-release harbor/harbor +``` + +## Uninstallation + +To uninstall/delete the `my-release` deployment: +```bash +helm uninstall my-release +``` + +## Configuration + +The following table lists the configurable parameters of the Harbor chart and the default values. + +| Parameter | Description | Default | +| -------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------- | +| **Expose** | | | +| `expose.type` | How to expose the service: `ingress`, `clusterIP`, `nodePort` or `loadBalancer`, other values will be ignored and the creation of service will be skipped. | `ingress` | +| `expose.tls.enabled` | Enable TLS or not. Delete the `ssl-redirect` annotations in `expose.ingress.annotations` when TLS is disabled and `expose.type` is `ingress`. Note: if the `expose.type` is `ingress` and TLS is disabled, the port must be included in the command when pulling/pushing images. Refer to https://github.com/goharbor/harbor/issues/5291 for details. | `true` | +| `expose.tls.certSource` | The source of the TLS certificate. Set as `auto`, `secret` or `none` and fill the information in the corresponding section: 1) auto: generate the TLS certificate automatically 2) secret: read the TLS certificate from the specified secret. The TLS certificate can be generated manually or by cert manager 3) none: configure no TLS certificate for the ingress. If the default TLS certificate is configured in the ingress controller, choose this option | `auto` | +| `expose.tls.auto.commonName` | The common name used to generate the certificate, it's necessary when the type isn't `ingress` | | +| `expose.tls.secret.secretName` | The name of secret which contains keys named: `tls.crt` - the certificate; `tls.key` - the private key | | +| `expose.tls.secret.notarySecretName` | The name of secret which contains keys named: `tls.crt` - the certificate; `tls.key` - the private key. Only needed when the `expose.type` is `ingress` | | +| `expose.ingress.hosts.core` | The host of Harbor core service in ingress rule | `core.harbor.domain` | +| `expose.ingress.hosts.notary` | The host of Harbor Notary service in ingress rule | `notary.harbor.domain` | +| `expose.ingress.controller` | The ingress controller type. Currently supports `default`, `gce` and `ncp` | `default` | +| `expose.ingress.kubeVersionOverride` | Allows the ability to override the kubernetes version used while templating the ingress | | +| `expose.ingress.annotations` | The annotations used commonly for ingresses | | +| `expose.ingress.harbor.annotations` | The annotations specific to harbor ingress | {} | +| `expose.ingress.harbor.labels` | The labels specific to harbor ingress | {} | +| `expose.ingress.notary.annotations` | The annotations specific to notary ingress | {} | +| `expose.ingress.notary.labels` | The labels specific to notary ingress | {} | +| `expose.clusterIP.name` | The name of ClusterIP service | `harbor` | +| `expose.clusterIP.annotations` | The annotations attached to the ClusterIP service | {} | +| `expose.clusterIP.ports.httpPort` | The service port Harbor listens on when serving HTTP | `80` | +| `expose.clusterIP.ports.httpsPort` | The service port Harbor listens on when serving HTTPS | `443` | +| `expose.clusterIP.ports.notaryPort` | The service port Notary listens on. Only needed when `notary.enabled` is set to `true` | `4443` | +| `expose.nodePort.name` | The name of NodePort service | `harbor` | +| `expose.nodePort.ports.http.port` | The service port Harbor listens on when serving HTTP | `80` | +| `expose.nodePort.ports.http.nodePort` | The node port Harbor listens on when serving HTTP | `30002` | +| `expose.nodePort.ports.https.port` | The service port Harbor listens on when serving HTTPS | `443` | +| `expose.nodePort.ports.https.nodePort` | The node port Harbor listens on when serving HTTPS | `30003` | +| `expose.nodePort.ports.notary.port` | The service port Notary listens on. Only needed when `notary.enabled` is set to `true` | `4443` | +| `expose.nodePort.ports.notary.nodePort` | The node port Notary listens on. Only needed when `notary.enabled` is set to `true` | `30004` | +| `expose.loadBalancer.name` | The name of service | `harbor` | +| `expose.loadBalancer.IP` | The IP of the loadBalancer. It only works when loadBalancer supports assigning IP | `""` | +| `expose.loadBalancer.ports.httpPort` | The service port Harbor listens on when serving HTTP | `80` | +| `expose.loadBalancer.ports.httpsPort` | The service port Harbor listens on when serving HTTPS | `30002` | +| `expose.loadBalancer.ports.notaryPort` | The service port Notary listens on. Only needed when `notary.enabled` is set to `true` | | +| `expose.loadBalancer.annotations` | The annotations attached to the loadBalancer service | {} | +| `expose.loadBalancer.sourceRanges` | List of IP address ranges to assign to loadBalancerSourceRanges | [] | +| **Internal TLS** | | | +| `internalTLS.enabled` | Enable TLS for the components (chartmuseum, core, jobservice, portal, registry, trivy) | `false` | +| `internalTLS.certSource` | Method to provide TLS for the components, options are `auto`, `manual`, `secret`. | `auto` | +| `internalTLS.trustCa` | The content of trust CA, only available when `certSource` is `manual`. **Note**: all the internal certificates of the components must be issued by this CA | | +| `internalTLS.core.secretName` | The secret name for core component, only available when `certSource` is `secret`. The secret must contain keys named: `ca.crt` - the CA certificate which is used to issue internal key and crt pair for components and all Harbor components must be issued by the same CA, `tls.crt` - the content of the TLS cert file, `tls.key` - the content of the TLS key file. | | +| `internalTLS.core.crt` | Content of core's TLS cert file, only available when `certSource` is `manual` | | +| `internalTLS.core.key` | Content of core's TLS key file, only available when `certSource` is `manual` | | +| `internalTLS.jobservice.secretName` | The secret name for jobservice component, only available when `certSource` is `secret`. The secret must contain keys named: `ca.crt` - the CA certificate which is used to issue internal key and crt pair for components and all Harbor components must be issued by the same CA, `tls.crt` - the content of the TLS cert file, `tls.key` - the content of the TLS key file. | | +| `internalTLS.jobservice.crt` | Content of jobservice's TLS cert file, only available when `certSource` is `manual` | | +| `internalTLS.jobservice.key` | Content of jobservice's TLS key file, only available when `certSource` is `manual` | | +| `internalTLS.registry.secretName` | The secret name for registry component, only available when `certSource` is `secret`. The secret must contain keys named: `ca.crt` - the CA certificate which is used to issue internal key and crt pair for components and all Harbor components must be issued by the same CA, `tls.crt` - the content of the TLS cert file, `tls.key` - the content of the TLS key file. | | +| `internalTLS.registry.crt` | Content of registry's TLS cert file, only available when `certSource` is `manual` | | +| `internalTLS.registry.key` | Content of registry's TLS key file, only available when `certSource` is `manual` | | +| `internalTLS.portal.secretName` | The secret name for portal component, only available when `certSource` is `secret`. The secret must contain keys named: `ca.crt` - the CA certificate which is used to issue internal key and crt pair for components and all Harbor components must be issued by the same CA, `tls.crt` - the content of the TLS cert file, `tls.key` - the content of the TLS key file. | | +| `internalTLS.portal.crt` | Content of portal's TLS cert file, only available when `certSource` is `manual` | | +| `internalTLS.portal.key` | Content of portal's TLS key file, only available when `certSource` is `manual` | | +| `internalTLS.chartmuseum.secretName` | The secret name for chartmuseum component, only available when `certSource` is `secret`. The secret must contain keys named: `ca.crt` - the CA certificate which is used to issue internal key and crt pair for components and all Harbor components must be issued by the same CA, `tls.crt` - the content of the TLS cert file, `tls.key` - the content of the TLS key file. | | +| `internalTLS.chartmuseum.crt` | Content of chartmuseum's TLS cert file, only available when `certSource` is `manual` | | +| `internalTLS.chartmuseum.key` | Content of chartmuseum's TLS key file, only available when `certSource` is `manual` | | +| `internalTLS.trivy.secretName` | The secret name for trivy component, only available when `certSource` is `secret`. The secret must contain keys named: `ca.crt` - the CA certificate which is used to issue internal key and crt pair for components and all Harbor components must be issued by the same CA, `tls.crt` - the content of the TLS cert file, `tls.key` - the content of the TLS key file. | | +| `internalTLS.trivy.crt` | Content of trivy's TLS cert file, only available when `certSource` is `manual` | | +| `internalTLS.trivy.key` | Content of trivy's TLS key file, only available when `certSource` is `manual` | | +| **IPFamily** | | | +| `ipFamily.ipv4.enabled` | if cluster is ipv4 enabled, all ipv4 related configs will set correspondingly, but currently it only affects the nginx related components | `true` | +| `ipFamily.ipv6.enabled` | if cluster is ipv6 enabled, all ipv6 related configs will set correspondingly, but currently it only affects the nginx related components | `true` | +| **Persistence** | | | +| `persistence.enabled` | Enable the data persistence or not | `true` | +| `persistence.resourcePolicy` | Setting it to `keep` to avoid removing PVCs during a helm delete operation. Leaving it empty will delete PVCs after the chart deleted. Does not affect PVCs created for internal database and redis components. | `keep` | +| `persistence.persistentVolumeClaim.registry.existingClaim` | Use the existing PVC which must be created manually before bound, and specify the `subPath` if the PVC is shared with other components | | +| `persistence.persistentVolumeClaim.registry.storageClass` | Specify the `storageClass` used to provision the volume. Or the default StorageClass will be used (the default). Set it to `-` to disable dynamic provisioning | | +| `persistence.persistentVolumeClaim.registry.subPath` | The sub path used in the volume | | +| `persistence.persistentVolumeClaim.registry.accessMode` | The access mode of the volume | `ReadWriteOnce` | +| `persistence.persistentVolumeClaim.registry.size` | The size of the volume | `5Gi` | +| `persistence.persistentVolumeClaim.registry.annotations` | The annotations of the volume | | +| `persistence.persistentVolumeClaim.chartmuseum.existingClaim` | Use the existing PVC which must be created manually before bound, and specify the `subPath` if the PVC is shared with other components | | +| `persistence.persistentVolumeClaim.chartmuseum.storageClass` | Specify the `storageClass` used to provision the volume. Or the default StorageClass will be used (the default). Set it to `-` to disable dynamic provisioning | | +| `persistence.persistentVolumeClaim.chartmuseum.subPath` | The sub path used in the volume | | +| `persistence.persistentVolumeClaim.chartmuseum.accessMode` | The access mode of the volume | `ReadWriteOnce` | +| `persistence.persistentVolumeClaim.chartmuseum.size` | The size of the volume | `5Gi` | +| `persistence.persistentVolumeClaim.chartmuseum.annotations` | The annotations of the volume | | +| `persistence.persistentVolumeClaim.jobservice.existingClaim` | Use the existing PVC which must be created manually before bound, and specify the `subPath` if the PVC is shared with other components | | +| `persistence.persistentVolumeClaim.jobservice.storageClass` | Specify the `storageClass` used to provision the volume. Or the default StorageClass will be used (the default). Set it to `-` to disable dynamic provisioning | | +| `persistence.persistentVolumeClaim.jobservice.subPath` | The sub path used in the volume | | +| `persistence.persistentVolumeClaim.jobservice.accessMode` | The access mode of the volume | `ReadWriteOnce` | +| `persistence.persistentVolumeClaim.jobservice.size` | The size of the volume | `1Gi` | +| `persistence.persistentVolumeClaim.jobservice.annotations` | The annotations of the volume | | +| `persistence.persistentVolumeClaim.database.existingClaim` | Use the existing PVC which must be created manually before bound, and specify the `subPath` if the PVC is shared with other components. If external database is used, the setting will be ignored | | +| `persistence.persistentVolumeClaim.database.storageClass` | Specify the `storageClass` used to provision the volume. Or the default StorageClass will be used (the default). Set it to `-` to disable dynamic provisioning. If external database is used, the setting will be ignored | | +| `persistence.persistentVolumeClaim.database.subPath` | The sub path used in the volume. If external database is used, the setting will be ignored | | +| `persistence.persistentVolumeClaim.database.accessMode` | The access mode of the volume. If external database is used, the setting will be ignored | `ReadWriteOnce` | +| `persistence.persistentVolumeClaim.database.size` | The size of the volume. If external database is used, the setting will be ignored | `1Gi` | +| `persistence.persistentVolumeClaim.database.annotations` | The annotations of the volume | | +| `persistence.persistentVolumeClaim.redis.existingClaim` | Use the existing PVC which must be created manually before bound, and specify the `subPath` if the PVC is shared with other components. If external Redis is used, the setting will be ignored | | +| `persistence.persistentVolumeClaim.redis.storageClass` | Specify the `storageClass` used to provision the volume. Or the default StorageClass will be used (the default). Set it to `-` to disable dynamic provisioning. If external Redis is used, the setting will be ignored | | +| `persistence.persistentVolumeClaim.redis.subPath` | The sub path used in the volume. If external Redis is used, the setting will be ignored | | +| `persistence.persistentVolumeClaim.redis.accessMode` | The access mode of the volume. If external Redis is used, the setting will be ignored | `ReadWriteOnce` | +| `persistence.persistentVolumeClaim.redis.size` | The size of the volume. If external Redis is used, the setting will be ignored | `1Gi` | +| `persistence.persistentVolumeClaim.redis.annotations` | The annotations of the volume | | +| `persistence.persistentVolumeClaim.trivy.existingClaim` | Use the existing PVC which must be created manually before bound, and specify the `subPath` if the PVC is shared with other components. | | +| `persistence.persistentVolumeClaim.trivy.storageClass` | Specify the `storageClass` used to provision the volume. Or the default StorageClass will be used (the default). Set it to `-` to disable dynamic provisioning | | +| `persistence.persistentVolumeClaim.trivy.subPath` | The sub path used in the volume | | +| `persistence.persistentVolumeClaim.trivy.accessMode` | The access mode of the volume | `ReadWriteOnce` | +| `persistence.persistentVolumeClaim.trivy.size` | The size of the volume | `5Gi` | +| `persistence.persistentVolumeClaim.trivy.annotations` | The annotations of the volume | | +| `persistence.imageChartStorage.disableredirect` | The configuration for managing redirects from content backends. For backends which not supported it (such as using minio for `s3` storage type), please set it to `true` to disable redirects. Refer to the [guide](https://github.com/docker/distribution/blob/master/docs/configuration.md#redirect) for more details | `false` | +| `persistence.imageChartStorage.caBundleSecretName` | Specify the `caBundleSecretName` if the storage service uses a self-signed certificate. The secret must contain keys named `ca.crt` which will be injected into the trust store of registry's and chartmuseum's containers. | | +| `persistence.imageChartStorage.type` | The type of storage for images and charts: `filesystem`, `azure`, `gcs`, `s3`, `swift` or `oss`. The type must be `filesystem` if you want to use persistent volumes for registry and chartmuseum. Refer to the [guide](https://github.com/docker/distribution/blob/master/docs/configuration.md#storage) for more details | `filesystem` | +| **General** | | | +| `externalURL` | The external URL for Harbor core service | `https://core.harbor.domain` | +| `caBundleSecretName` | The custom CA bundle secret name, the secret must contain key named "ca.crt" which will be injected into the trust store for chartmuseum, core, jobservice, registry, trivy components. | | +| `uaaSecretName` | If using external UAA auth which has a self signed cert, you can provide a pre-created secret containing it under the key `ca.crt`. | | +| `imagePullPolicy` | The image pull policy | | +| `imagePullSecrets` | The imagePullSecrets names for all deployments | | +| `updateStrategy.type` | The update strategy for deployments with persistent volumes(jobservice, registry and chartmuseum): `RollingUpdate` or `Recreate`. Set it as `Recreate` when `RWM` for volumes isn't supported | `RollingUpdate` | +| `logLevel` | The log level: `debug`, `info`, `warning`, `error` or `fatal` | `info` | +| `harborAdminPassword` | The initial password of Harbor admin. Change it from portal after launching Harbor | `Harbor12345` | +| `caSecretName` | The name of the secret which contains key named `ca.crt`. Setting this enables the download link on portal to download the CA certificate when the certificate isn't generated automatically | | +| `secretKey` | The key used for encryption. Must be a string of 16 chars | `not-a-secure-key` | +| `proxy.httpProxy` | The URL of the HTTP proxy server | | +| `proxy.httpsProxy` | The URL of the HTTPS proxy server | | +| `proxy.noProxy` | The URLs that the proxy settings not apply to | 127.0.0.1,localhost,.local,.internal | +| `proxy.components` | The component list that the proxy settings apply to | core, jobservice, trivy | +| `enableMigrateHelmHook` | Run the migration job via helm hook, if it is true, the database migration will be separated from harbor-core, run with a preupgrade job migration-job | `false` | +| **Nginx** (if service exposed via `ingress`, Nginx will not be used) | | | +| `nginx.image.repository` | Image repository | `goharbor/nginx-photon` | +| `nginx.image.tag` | Image tag | `dev` | +| `nginx.replicas` | The replica count | `1` | +| `nginx.revisionHistoryLimit` | The revision history limit | `10` | +| `nginx.resources` | The [resources] to allocate for container | undefined | +| `nginx.automountServiceAccountToken` | Mount serviceAccountToken? | `false` | +| `nginx.nodeSelector` | Node labels for pod assignment | `{}` | +| `nginx.tolerations` | Tolerations for pod assignment | `[]` | +| `nginx.affinity` | Node/Pod affinities | `{}` | +| `nginx.podAnnotations` | Annotations to add to the nginx pod | `{}` | +| `nginx.priorityClassName` | The priority class to run the pod as | | +| **Portal** | | | +| `portal.image.repository` | Repository for portal image | `goharbor/harbor-portal` | +| `portal.image.tag` | Tag for portal image | `dev` | +| `portal.replicas` | The replica count | `1` | +| `portal.revisionHistoryLimit` | The revision history limit | `10` | +| `portal.resources` | The [resources] to allocate for container | undefined | +| `portal.automountServiceAccountToken` | Mount serviceAccountToken? | `false` | +| `portal.nodeSelector` | Node labels for pod assignment | `{}` | +| `portal.tolerations` | Tolerations for pod assignment | `[]` | +| `portal.affinity` | Node/Pod affinities | `{}` | +| `portal.podAnnotations` | Annotations to add to the portal pod | `{}` | +| `portal.priorityClassName` | The priority class to run the pod as | | +| **Core** | | | +| `core.image.repository` | Repository for Harbor core image | `goharbor/harbor-core` | +| `core.image.tag` | Tag for Harbor core image | `dev` | +| `core.replicas` | The replica count | `1` | +| `core.revisionHistoryLimit` | The revision history limit | `10` | +| `core.startupProbe.initialDelaySeconds` | The initial delay in seconds for the startup probe | `10` | +| `core.resources` | The [resources] to allocate for container | undefined | +| `core.automountServiceAccountToken` | Mount serviceAccountToken? | `false` | +| `core.nodeSelector` | Node labels for pod assignment | `{}` | +| `core.tolerations` | Tolerations for pod assignment | `[]` | +| `core.affinity` | Node/Pod affinities | `{}` | +| `core.podAnnotations` | Annotations to add to the core pod | `{}` | +| `core.secret` | Secret is used when core server communicates with other components. If a secret key is not specified, Helm will generate one. Must be a string of 16 chars. | | +| `core.secretName` | Fill the name of a kubernetes secret if you want to use your own TLS certificate and private key for token encryption/decryption. The secret must contain keys named: `tls.crt` - the certificate and `tls.key` - the private key. The default key pair will be used if it isn't set | | +| `core.xsrfKey` | The XSRF key. Will be generated automatically if it isn't specified | | +| `core.priorityClassName` | The priority class to run the pod as | | +| `core.artifactPullAsyncFlushDuration` | The time duration for async update artifact pull_time and repository pull_count | | +| **Jobservice** | | | +| `jobservice.image.repository` | Repository for jobservice image | `goharbor/harbor-jobservice` | +| `jobservice.image.tag` | Tag for jobservice image | `dev` | +| `jobservice.replicas` | The replica count | `1` | +| `jobservice.revisionHistoryLimit` | The revision history limit | `10` | +| `jobservice.maxJobWorkers` | The max job workers | `10` | +| `jobservice.jobLoggers` | The loggers for jobs: `file`, `database` or `stdout` | `file` | +| `jobservice.loggerSweeperDuration` | The jobLogger sweeper duration in days (ignored if `jobLoggers` is set to `stdout`) | `14` | +| `jobservice.resources` | The [resources] to allocate for container | undefined | +| `jobservice.automountServiceAccountToken` | Mount serviceAccountToken? | `false` | +| `jobservice.nodeSelector` | Node labels for pod assignment | `{}` | +| `jobservice.tolerations` | Tolerations for pod assignment | `[]` | +| `jobservice.affinity` | Node/Pod affinities | `{}` | +| `jobservice.podAnnotations` | Annotations to add to the jobservice pod | `{}` | +| `jobservice.priorityClassName` | The priority class to run the pod as | | +| `jobservice.secret` | Secret is used when job service communicates with other components. If a secret key is not specified, Helm will generate one. Must be a string of 16 chars. | | +| **Registry** | | | +| `registry.registry.image.repository` | Repository for registry image | `goharbor/registry-photon` | +| `registry.registry.image.tag` | Tag for registry image | `dev` | +| `registry.registry.resources` | The [resources] to allocate for container | undefined | +| `registry.controller.image.repository` | Repository for registry controller image | `goharbor/harbor-registryctl` | +| `registry.controller.image.tag` | Tag for registry controller image | `dev` | +| `registry.controller.resources` | The [resources] to allocate for container | undefined | +| `registry.replicas` | The replica count | `1` | +| `registry.revisionHistoryLimit` | The revision history limit | `10` | +| `registry.nodeSelector` | Node labels for pod assignment | `{}` | +| `registry.automountServiceAccountToken` | Mount serviceAccountToken? | `false` | +| `registry.tolerations` | Tolerations for pod assignment | `[]` | +| `registry.affinity` | Node/Pod affinities | `{}` | +| `registry.middleware` | Middleware is used to add support for a CDN between backend storage and `docker pull` recipient. See [official docs](https://github.com/docker/distribution/blob/master/docs/configuration.md#middleware). | | +| `registry.podAnnotations` | Annotations to add to the registry pod | `{}` | +| `registry.priorityClassName` | The priority class to run the pod as | | +| `registry.secret` | Secret is used to secure the upload state from client and registry storage backend. See [official docs](https://github.com/docker/distribution/blob/master/docs/configuration.md#http). If a secret key is not specified, Helm will generate one. Must be a string of 16 chars. | | +| `registry.credentials.username` | The username for accessing the registry instance, which is hosted by htpasswd auth mode. More details see [official docs](https://github.com/docker/distribution/blob/master/docs/configuration.md#htpasswd). | `harbor_registry_user` | +| `registry.credentials.password` | The password for accessing the registry instance, which is hosted by htpasswd auth mode. More details see [official docs](https://github.com/docker/distribution/blob/master/docs/configuration.md#htpasswd). It is suggested you update this value before installation. | `harbor_registry_password` | +| `registry.credentials.htpasswdString` | Login and password in htpasswd string format. Excludes `registry.credentials.username` and `registry.credentials.password`. May come in handy when integrating with tools like argocd or flux. This allows the same line to be generated each time the template is rendered, instead of the `htpasswd` function from helm, which generates different lines each time because of the salt. | undefined | +| `registry.relativeurls` | If true, the registry returns relative URLs in Location headers. The client is responsible for resolving the correct URL. Needed if harbor is behind a reverse proxy | `false` | +| `registry.upload_purging.enabled` | If true, enable purge _upload directories | `true` | +| `registry.upload_purging.age` | Remove files in _upload directories which exist for a period of time, default is one week. | `168h` | +| `registry.upload_purging.interval` | The interval of the purge operations | `24h` | +| `registry.upload_purging.dryrun` | If true, enable dryrun for purging _upload, default false | `false` | +| **Chartmuseum** | | | +| `chartmuseum.enabled` | Enable chartmusuem to store chart | `true` | +| `chartmuseum.absoluteUrl` | If true, ChartMuseum will return absolute URLs. The default behavior is to return relative URLs | `false` | +| `chartmuseum.image.repository` | Repository for chartmuseum image | `goharbor/chartmuseum-photon` | +| `chartmuseum.image.tag` | Tag for chartmuseum image | `dev` | +| `chartmuseum.replicas` | The replica count | `1` | +| `chartmuseum.revisionHistoryLimit` | The revision history limit | `10` | +| `chartmuseum.resources` | The [resources] to allocate for container | undefined | +| `chartmuseum.automountServiceAccountToken` | Mount serviceAccountToken? | `false` | +| `chartmuseum.nodeSelector` | Node labels for pod assignment | `{}` | +| `chartmuseum.tolerations` | Tolerations for pod assignment | `[]` | +| `chartmuseum.affinity` | Node/Pod affinities | `{}` | +| `chartmuseum.podAnnotations` | Annotations to add to the chart museum pod | `{}` | +| `chartmuseum.priorityClassName` | The priority class to run the pod as | | +| **[Trivy][trivy]** | | | +| `trivy.enabled` | The flag to enable Trivy scanner | `true` | +| `trivy.image.repository` | Repository for Trivy adapter image | `goharbor/trivy-adapter-photon` | +| `trivy.image.tag` | Tag for Trivy adapter image | `dev` | +| `trivy.resources` | The [resources] to allocate for Trivy adapter container | | +| `trivy.automountServiceAccountToken` | Mount serviceAccountToken? | `false` | +| `trivy.replicas` | The number of Pod replicas | `1` | +| `trivy.debugMode` | The flag to enable Trivy debug mode | `false` | +| `trivy.vulnType` | Comma-separated list of vulnerability types. Possible values `os` and `library`. | `os,library` | +| `trivy.severity` | Comma-separated list of severities to be checked | `UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL` | +| `trivy.ignoreUnfixed` | The flag to display only fixed vulnerabilities | `false` | +| `trivy.insecure` | The flag to skip verifying registry certificate | `false` | +| `trivy.skipUpdate` | The flag to disable [Trivy DB][trivy-db] downloads from GitHub | `false` | +| `trivy.offlineScan` | The flag prevents Trivy from sending API requests to identify dependencies. | `false` | +| `trivy.timeout` | The duration to wait for scan completion | `5m0s` | +| `trivy.gitHubToken` | The GitHub access token to download [Trivy DB][trivy-db] (see [GitHub rate limiting][trivy-rate-limiting]) | | +| `trivy.priorityClassName` | The priority class to run the pod as | | +| **Notary** | | | +| `notary.enabled` | Enable Notary? | `true` | +| `notary.server.image.repository` | Repository for notary server image | `goharbor/notary-server-photon` | +| `notary.server.image.tag` | Tag for notary server image | `dev` | +| `notary.server.replicas` | The replica count | `1` | +| `notary.server.resources` | The [resources] to allocate for container | undefined | +| `notary.server.priorityClassName` | The priority class to run the pod as | | +| `notary.server.automountServiceAccountToken` | Mount serviceAccountToken? | `false` | +| `notary.signer.image.repository` | Repository for notary signer image | `goharbor/notary-signer-photon` | +| `notary.signer.image.tag` | Tag for notary signer image | `dev` | +| `notary.signer.replicas` | The replica count | `1` | +| `notary.signer.resources` | The [resources] to allocate for container | undefined | +| `notary.signer.priorityClassName` | The priority class to run the pod as | | +| `notary.signer.automountServiceAccountToken` | Mount serviceAccountToken? | `false` | +| `notary.nodeSelector` | Node labels for pod assignment | `{}` | +| `notary.tolerations` | Tolerations for pod assignment | `[]` | +| `notary.affinity` | Node/Pod affinities | `{}` | +| `notary.podAnnotations` | Annotations to add to the notary pod | `{}` | +| `notary.secretName` | Fill the name of a kubernetes secret if you want to use your own TLS certificate authority, certificate and private key for notary communications. The secret must contain keys named `tls.ca`, `tls.crt` and `tls.key` that contain the CA, certificate and private key. They will be generated if not set. | | +| **Database** | | | +| `database.type` | If external database is used, set it to `external` | `internal` | +| `database.internal.image.repository` | Repository for database image | `goharbor/harbor-db` | +| `database.internal.image.tag` | Tag for database image | `dev` | +| `database.internal.password` | The password for database | `changeit` | +| `database.internal.shmSizeLimit` | The limit for the size of shared memory for internal PostgreSQL, conventionally it's around 50% of the memory limit of the container | `512Mi` | +| `database.internal.resources` | The [resources] to allocate for container | undefined | +| `database.internal.automountServiceAccountToken` | Mount serviceAccountToken? | `false` | +| `database.internal.initContainer.migrator.resources` | The [resources] to allocate for the database migrator initContainer | undefined | +| `database.internal.initContainer.permissions.resources` | The [resources] to allocate for the database permissions initContainer | undefined | +| `database.internal.nodeSelector` | Node labels for pod assignment | `{}` | +| `database.internal.tolerations` | Tolerations for pod assignment | `[]` | +| `database.internal.affinity` | Node/Pod affinities | `{}` | +| `database.internal.priorityClassName` | The priority class to run the pod as | | +| `database.external.host` | The hostname of external database | `192.168.0.1` | +| `database.external.port` | The port of external database | `5432` | +| `database.external.username` | The username of external database | `user` | +| `database.external.password` | The password of external database | `password` | +| `database.external.coreDatabase` | The database used by core service | `registry` | +| `database.external.notaryServerDatabase` | The database used by Notary server | `notary_server` | +| `database.external.notarySignerDatabase` | The database used by Notary signer | `notary_signer` | +| `database.external.sslmode` | Connection method of external database (require, verify-full, verify-ca, disable) | `disable` | +| `database.maxIdleConns` | The maximum number of connections in the idle connection pool. If it <=0, no idle connections are retained. | `50` | +| `database.maxOpenConns` | The maximum number of open connections to the database. If it <= 0, then there is no limit on the number of open connections. | `100` | +| `database.podAnnotations` | Annotations to add to the database pod | `{}` | +| **Redis** | | | +| `redis.type` | If external redis is used, set it to `external` | `internal` | +| `redis.internal.image.repository` | Repository for redis image | `goharbor/redis-photon` | +| `redis.internal.image.tag` | Tag for redis image | `dev` | +| `redis.internal.resources` | The [resources] to allocate for container | undefined | +| `redis.internal.automountServiceAccountToken` | Mount serviceAccountToken? | `false` | +| `redis.internal.nodeSelector` | Node labels for pod assignment | `{}` | +| `redis.internal.tolerations` | Tolerations for pod assignment | `[]` | +| `redis.internal.affinity` | Node/Pod affinities | `{}` | +| `redis.internal.priorityClassName` | The priority class to run the pod as | | +| `redis.external.addr` | The addr of external Redis: :. When using sentinel, it should be :,:,: | `192.168.0.2:6379` | +| `redis.external.sentinelMasterSet` | The name of the set of Redis instances to monitor | | +| `redis.external.coreDatabaseIndex` | The database index for core | `0` | +| `redis.external.jobserviceDatabaseIndex` | The database index for jobservice | `1` | +| `redis.external.registryDatabaseIndex` | The database index for registry | `2` | +| `redis.external.chartmuseumDatabaseIndex` | The database index for chartmuseum | `3` | +| `redis.external.trivyAdapterIndex` | The database index for trivy adapter | `5` | +| `redis.external.password` | The password of external Redis | | +| `redis.podAnnotations` | Annotations to add to the redis pod | `{}` | +| **Exporter** | | | +| `exporter.replicas` | The replica count | `1` | +| `exporter.revisionHistoryLimit` | The revision history limit | `10` | +| `exporter.podAnnotations` | Annotations to add to the exporter pod | `{}` | +| `exporter.image.repository` | Repository for redis image | `goharbor/harbor-exporter` | +| `exporter.image.tag` | Tag for exporter image | `dev` | +| `exporter.nodeSelector` | Node labels for pod assignment | `{}` | +| `exporter.tolerations` | Tolerations for pod assignment | `[]` | +| `exporter.affinity` | Node/Pod affinities | `{}` | +| `exporter.automountServiceAccountToken` | Mount serviceAccountToken? | `false` | +| `exporter.cacheDuration` | the cache duration for information that exporter collected from Harbor | `30` | +| `exporter.cacheCleanInterval` | cache clean interval for information that exporter collected from Harbor | `14400` | +| `exporter.priorityClassName` | The priority class to run the pod as | | +| **Metrics** | | | +| `metrics.enabled` | if enable harbor metrics | `false` | +| `metrics.core.path` | the url path for core metrics | `/metrics` | +| `metrics.core.port` | the port for core metrics | `8001` | +| `metrics.registry.path` | the url path for registry metrics | `/metrics` | +| `metrics.registry.port` | the port for registry metrics | `8001` | +| `metrics.exporter.path` | the url path for exporter metrics | `/metrics` | +| `metrics.exporter.port` | the port for exporter metrics | `8001` | +| `metrics.serviceMonitor.enabled` | create prometheus serviceMonitor. Requires prometheus CRD's | `false` | +| `metrics.serviceMonitor.additionalLabels` | additional labels to upsert to the manifest | `""` | +| `metrics.serviceMonitor.interval` | scrape period for harbor metrics | `""` | +| `metrics.serviceMonitor.metricRelabelings` | metrics relabel to add/mod/del before ingestion | `[]` | +| `metrics.serviceMonitor.relabelings` | relabels to add/mod/del to sample before scrape | `[]` | +| **Trace** | | | +| `trace.enabled` | Enable tracing or not | `false` | +| `trace.provider` | The tracing provider: `jaeger` or `otel`. `jaeger` should be 1.26+ | `jaeger` | +| `trace.sample_rate` | Set `sample_rate` to 1 if you want sampling 100% of trace data; set 0.5 if you want sampling 50% of trace data, and so forth | `1` | +| `trace.namespace` | Namespace used to differentiate different harbor services | | +| `trace.attributes` | `attributes` is a key value dict contains user defined attributes used to initialize trace provider | | +| `trace.jaeger.endpoint` | The endpoint of jaeger | `http://hostname:14268/api/traces` | +| `trace.jaeger.username` | The username of jaeger | | +| `trace.jaeger.password` | The password of jaeger | | +| `trace.jaeger.agent_host` | The agent host of jaeger | | +| `trace.jaeger.agent_port` | The agent port of jaeger | `6831` | +| `trace.otel.endpoint` | The endpoint of otel | `hostname:4318` | +| `trace.otel.url_path` | The URL path of otel | `/v1/traces` | +| `trace.otel.compression` | Whether enable compression or not for otel | `false` | +| `trace.otel.insecure` | Whether establish insecure connection or not for otel | `true` | +| `trace.otel.timeout` | The timeout of otel | `10s` | + +[resources]: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/ +[trivy]: https://github.com/aquasecurity/trivy +[trivy-db]: https://github.com/aquasecurity/trivy-db +[trivy-rate-limiting]: https://github.com/aquasecurity/trivy#github-rate-limiting diff --git a/ResearchOps/harbor/cert/tls.crt b/ResearchOps/harbor/cert/tls.crt new file mode 100644 index 0000000..de4aa3e --- /dev/null +++ b/ResearchOps/harbor/cert/tls.crt @@ -0,0 +1,28 @@ +-----BEGIN CERTIFICATE----- +MIIE0zCCArugAwIBAgIJAPY/OzLMeVq2MA0GCSqGSIb3DQEBCwUAMAAwHhcNMTkw +NDE4MDIyNzM3WhcNMjkwNDE1MDIyNzM3WjAAMIICIjANBgkqhkiG9w0BAQEFAAOC +Ag8AMIICCgKCAgEA3xlUJs2b/aI2NLoy4OIQ+dn/yMb/O99iKDRyZKpH8rSOmS+o +F9unmSAzL65XA/v6nY0OLI/dASDjkqkBpIdTGzogR5f8UiB6osuEY7V71XZdzWLr +PjnJq6ZLAaoKmwG80W5+Wd6V8PygOx52mkr1w7IWKz+1ZLI5izbppon7XVGVRaAT +RvNZDiJ6CeJpcJ5H723lkf5RvJWatZLCYIYDbRfTiKsyQ/SlRcv5BVfHg/LJSH9Q +LGRhPMARldl9wyZCwZZDHxheI4a+26aa8MY3u9st/l0/Oo6VCTGpMiEhiGF2LVjp +UWq/+BP4SFEvJfq/DuinI139W/5aZZ7/HwRPlmYU6pXTRLyIg7jd+19fJwR7X37q +w0o8t06FhjmrCzaYCUjoReqDmHaNmZN/ddvG7jZWBu+jNh0YavsyQyCIVmv6yqSc +jPiD9uivxqTwjJidIBRfuUrz3aERQ7cQgf0qhqjIzflzHbFKhILocBWq7zyNl9hr +vUGT/WZcw0t/OtM72SPaplmTgVbbQRxf2VHzyptGIvtydlXK8thxOMpXo4e+Sl8d +1gdQcC4oisN9F29oNs8P5yFQP//xYuv8C607nCj1DzrId5avG/NVfKB/fbDKEFgN +2WhHInTzPLEcjF4fErcUAEuWW0buX/6FHCG3iTtrqyD92KTVDfN1J56rrcsCAwEA +AaNQME4wHQYDVR0OBBYEFFhNhTo4UAC2PUsf8jYaWj160vGEMB8GA1UdIwQYMBaA +FFhNhTo4UAC2PUsf8jYaWj160vGEMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggIBAMAsEtVlELMwdtcifHeOT0kOmf5wo9In/eFSgscCzBMDaRx2B3q36AoS +Il7XWAZpevaR7W7yeARKaAshBLhygUqLD0zWbKlSN9Hprd1wdpM0ffyPpN5dxOYA +er04y12GRnCbMYqi4cvztP4TinXqq2yHSYhLbO9qkI5gbWVxkRuIcMKvixddllNY +Q3obJaDDHmovM3+g/G+1YFgt4qES38XnJ7BrSshHnn5EIQh286xfJriyrK2hHbLJ +qz0YuF6G3DXPeWGgXvj0Hipc0f8UDZkKkk/eGEI6vEkytyvoepoZI2XbAf/ZMy5n +KwuhEn4hhkFMwWaSWp/h0QdMCaxk4BVSOqmNVaLSB7+FjsIj4CasFotYiyJ2gpRB +Nf8QaS4bz0Tn1eBbC8ksj+e3ZWeX2b5wVMjql9jTt2X1ICs8KKe3vEBkjqT2AUi2 +52TtKzm73aWrz/GPy/Q2LCor3Fh9FGVSBOBBDXGy6MJpNHJnYVH9EENFGOh85ol1 +2pADOBB5vAU/kLB5LHPj2kue/FMiHaNnrSYIGrMlBSX2jj9EYa1uuUH+pd4MBj1F +5uH8ORiaQ6ht2+WHklxic1Rj5yTYQwVlH70CBOn+qVEdo63yQwzAMJKFIwlGUQEX +jiljgc86q4cZtUTFrcwMidbk+8Q6+JbDVg7HV/+pnC+wnv197kwe +-----END CERTIFICATE----- diff --git a/ResearchOps/harbor/cert/tls.key b/ResearchOps/harbor/cert/tls.key new file mode 100644 index 0000000..014e2fa --- /dev/null +++ b/ResearchOps/harbor/cert/tls.key @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/ResearchOps/harbor/conf/notary-server.json b/ResearchOps/harbor/conf/notary-server.json new file mode 100644 index 0000000..b3c2624 --- /dev/null +++ b/ResearchOps/harbor/conf/notary-server.json @@ -0,0 +1,28 @@ +{ + "server": { + "http_addr": ":4443" + }, + "trust_service": { + "type": "remote", + "hostname": "{{ template "harbor.notary-signer" . }}", + "port": "7899", + "tls_ca_file": "/etc/ssl/notary/ca.crt", + "key_algorithm": "ecdsa" + }, + "logging": { + "level": "{{ .Values.logLevel }}" + }, + "storage": { + "backend": "postgres", + "db_url": "{{ template "harbor.database.notaryServer" . }}" + }, + "auth": { + "type": "token", + "options": { + "realm": "{{ .Values.externalURL }}/service/token", + "service": "harbor-notary", + "issuer": "harbor-token-issuer", + "rootcertbundle": "/root.crt" + } + } +} \ No newline at end of file diff --git a/ResearchOps/harbor/conf/notary-signer.json b/ResearchOps/harbor/conf/notary-signer.json new file mode 100644 index 0000000..75a4d68 --- /dev/null +++ b/ResearchOps/harbor/conf/notary-signer.json @@ -0,0 +1,15 @@ +{ + "server": { + "grpc_addr": ":7899", + "tls_cert_file": "/etc/ssl/notary/tls.crt", + "tls_key_file": "/etc/ssl/notary/tls.key" + }, + "logging": { + "level": "{{ .Values.logLevel }}" + }, + "storage": { + "backend": "postgres", + "db_url": "{{ template "harbor.database.notarySigner" . }}", + "default_alias": "defaultalias" + } +} \ No newline at end of file diff --git a/ResearchOps/harbor/templates/Certificate.yaml b/ResearchOps/harbor/templates/Certificate.yaml new file mode 100644 index 0000000..36b469a --- /dev/null +++ b/ResearchOps/harbor/templates/Certificate.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: harbor-abu-pub-tls + namespace: harbor +spec: + dnsNames: + - harbor.abu.pub + - notary.abu.pub + issuerRef: + kind: ClusterIssuer + name: letsencrypt-dns01 + secretName: harbor-abu-pub-tls diff --git a/ResearchOps/harbor/templates/NOTES.txt b/ResearchOps/harbor/templates/NOTES.txt new file mode 100644 index 0000000..0980c08 --- /dev/null +++ b/ResearchOps/harbor/templates/NOTES.txt @@ -0,0 +1,3 @@ +Please wait for several minutes for Harbor deployment to complete. +Then you should be able to visit the Harbor portal at {{ .Values.externalURL }} +For more details, please visit https://github.com/goharbor/harbor diff --git a/ResearchOps/harbor/templates/_helpers.tpl b/ResearchOps/harbor/templates/_helpers.tpl new file mode 100644 index 0000000..5eb9910 --- /dev/null +++ b/ResearchOps/harbor/templates/_helpers.tpl @@ -0,0 +1,606 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "harbor.name" -}} +{{- default "harbor" .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "harbor.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default "harbor" .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* Helm required labels */}} +{{- define "harbor.labels" -}} +heritage: {{ .Release.Service }} +release: {{ .Release.Name }} +chart: {{ .Chart.Name }} +app: "{{ template "harbor.name" . }}" +{{- end -}} + +{{/* matchLabels */}} +{{- define "harbor.matchLabels" -}} +release: {{ .Release.Name }} +app: "{{ template "harbor.name" . }}" +{{- end -}} + +{{- define "harbor.autoGenCert" -}} + {{- if and .Values.expose.tls.enabled (eq .Values.expose.tls.certSource "auto") -}} + {{- printf "true" -}} + {{- else -}} + {{- printf "false" -}} + {{- end -}} +{{- end -}} + +{{- define "harbor.autoGenCertForIngress" -}} + {{- if and (eq (include "harbor.autoGenCert" .) "true") (eq .Values.expose.type "ingress") -}} + {{- printf "true" -}} + {{- else -}} + {{- printf "false" -}} + {{- end -}} +{{- end -}} + +{{- define "harbor.autoGenCertForNginx" -}} + {{- if and (eq (include "harbor.autoGenCert" .) "true") (ne .Values.expose.type "ingress") -}} + {{- printf "true" -}} + {{- else -}} + {{- printf "false" -}} + {{- end -}} +{{- end -}} + +{{- define "harbor.database.host" -}} + {{- if eq .Values.database.type "internal" -}} + {{- template "harbor.database" . }} + {{- else -}} + {{- .Values.database.external.host -}} + {{- end -}} +{{- end -}} + +{{- define "harbor.database.port" -}} + {{- if eq .Values.database.type "internal" -}} + {{- printf "%s" "5432" -}} + {{- else -}} + {{- .Values.database.external.port -}} + {{- end -}} +{{- end -}} + +{{- define "harbor.database.username" -}} + {{- if eq .Values.database.type "internal" -}} + {{- printf "%s" "postgres" -}} + {{- else -}} + {{- .Values.database.external.username -}} + {{- end -}} +{{- end -}} + +{{- define "harbor.database.rawPassword" -}} + {{- if eq .Values.database.type "internal" -}} + {{- .Values.database.internal.password -}} + {{- else -}} + {{- .Values.database.external.password -}} + {{- end -}} +{{- end -}} + +{{- define "harbor.database.escapedRawPassword" -}} + {{- include "harbor.database.rawPassword" . | urlquery | replace "+" "%20" -}} +{{- end -}} + +{{- define "harbor.database.encryptedPassword" -}} + {{- include "harbor.database.rawPassword" . | b64enc | quote -}} +{{- end -}} + +{{- define "harbor.database.coreDatabase" -}} + {{- if eq .Values.database.type "internal" -}} + {{- printf "%s" "registry" -}} + {{- else -}} + {{- .Values.database.external.coreDatabase -}} + {{- end -}} +{{- end -}} + +{{- define "harbor.database.notaryServerDatabase" -}} + {{- if eq .Values.database.type "internal" -}} + {{- printf "%s" "notaryserver" -}} + {{- else -}} + {{- .Values.database.external.notaryServerDatabase -}} + {{- end -}} +{{- end -}} + +{{- define "harbor.database.notarySignerDatabase" -}} + {{- if eq .Values.database.type "internal" -}} + {{- printf "%s" "notarysigner" -}} + {{- else -}} + {{- .Values.database.external.notarySignerDatabase -}} + {{- end -}} +{{- end -}} + +{{- define "harbor.database.sslmode" -}} + {{- if eq .Values.database.type "internal" -}} + {{- printf "%s" "disable" -}} + {{- else -}} + {{- .Values.database.external.sslmode -}} + {{- end -}} +{{- end -}} + +{{- define "harbor.database.notaryServer" -}} +postgres://{{ template "harbor.database.username" . }}:{{ template "harbor.database.escapedRawPassword" . }}@{{ template "harbor.database.host" . }}:{{ template "harbor.database.port" . }}/{{ template "harbor.database.notaryServerDatabase" . }}?sslmode={{ template "harbor.database.sslmode" . }} +{{- end -}} + +{{- define "harbor.database.notarySigner" -}} +postgres://{{ template "harbor.database.username" . }}:{{ template "harbor.database.escapedRawPassword" . }}@{{ template "harbor.database.host" . }}:{{ template "harbor.database.port" . }}/{{ template "harbor.database.notarySignerDatabase" . }}?sslmode={{ template "harbor.database.sslmode" . }} +{{- end -}} + +{{- define "harbor.redis.scheme" -}} + {{- with .Values.redis }} + {{- ternary "redis+sentinel" "redis" (and (eq .type "external" ) (not (not .external.sentinelMasterSet))) }} + {{- end }} +{{- end -}} + +/*host:port*/ +{{- define "harbor.redis.addr" -}} + {{- with .Values.redis }} + {{- ternary (printf "%s:6379" (include "harbor.redis" $ )) .external.addr (eq .type "internal") }} + {{- end }} +{{- end -}} + +{{- define "harbor.redis.masterSet" -}} + {{- with .Values.redis }} + {{- ternary .external.sentinelMasterSet "" (eq "redis+sentinel" (include "harbor.redis.scheme" $)) }} + {{- end }} +{{- end -}} + +{{- define "harbor.redis.password" -}} + {{- with .Values.redis }} + {{- ternary "" .external.password (eq .type "internal") }} + {{- end }} +{{- end -}} + +/*scheme://[redis:password@]host:port[/master_set]*/ +{{- define "harbor.redis.url" -}} + {{- with .Values.redis }} + {{- $path := ternary "" (printf "/%s" (include "harbor.redis.masterSet" $)) (not (include "harbor.redis.masterSet" $)) }} + {{- $cred := ternary (printf "redis:%s@" (.external.password | urlquery)) "" (and (eq .type "external" ) (not (not .external.password))) }} + {{- printf "%s://%s%s%s" (include "harbor.redis.scheme" $) $cred (include "harbor.redis.addr" $) $path -}} + {{- end }} +{{- end -}} + +/*scheme://[redis:password@]addr/db_index?idle_timeout_seconds=30*/ +{{- define "harbor.redis.urlForCore" -}} + {{- with .Values.redis }} + {{- $index := ternary "0" .external.coreDatabaseIndex (eq .type "internal") }} + {{- printf "%s/%s?idle_timeout_seconds=30" (include "harbor.redis.url" $) $index -}} + {{- end }} +{{- end -}} + +/*scheme://[redis:password@]addr/db_index*/ +{{- define "harbor.redis.urlForJobservice" -}} + {{- with .Values.redis }} + {{- $index := ternary "1" .external.jobserviceDatabaseIndex (eq .type "internal") }} + {{- printf "%s/%s" (include "harbor.redis.url" $) $index -}} + {{- end }} +{{- end -}} + +/*scheme://[redis:password@]addr/db_index?idle_timeout_seconds=30*/ +{{- define "harbor.redis.urlForRegistry" -}} + {{- with .Values.redis }} + {{- $index := ternary "2" .external.registryDatabaseIndex (eq .type "internal") }} + {{- printf "%s/%s?idle_timeout_seconds=30" (include "harbor.redis.url" $) $index -}} + {{- end }} +{{- end -}} + +/*scheme://[redis:password@]addr/db_index?idle_timeout_seconds=30*/ +{{- define "harbor.redis.urlForTrivy" -}} + {{- with .Values.redis }} + {{- $index := ternary "5" .external.trivyAdapterIndex (eq .type "internal") }} + {{- printf "%s/%s?idle_timeout_seconds=30" (include "harbor.redis.url" $) $index -}} + {{- end }} +{{- end -}} + +{{- define "harbor.redis.dbForRegistry" -}} + {{- with .Values.redis }} + {{- ternary "2" .external.registryDatabaseIndex (eq .type "internal") }} + {{- end }} +{{- end -}} + +{{- define "harbor.redis.dbForChartmuseum" -}} + {{- with .Values.redis }} + {{- ternary "3" .external.chartmuseumDatabaseIndex (eq .type "internal") }} + {{- end }} +{{- end -}} + +{{- define "harbor.portal" -}} + {{- printf "%s-portal" (include "harbor.fullname" .) -}} +{{- end -}} + +{{- define "harbor.core" -}} + {{- printf "%s-core" (include "harbor.fullname" .) -}} +{{- end -}} + +{{- define "harbor.redis" -}} + {{- printf "%s-redis" (include "harbor.fullname" .) -}} +{{- end -}} + +{{- define "harbor.jobservice" -}} + {{- printf "%s-jobservice" (include "harbor.fullname" .) -}} +{{- end -}} + +{{- define "harbor.registry" -}} + {{- printf "%s-registry" (include "harbor.fullname" .) -}} +{{- end -}} + +{{- define "harbor.registryCtl" -}} + {{- printf "%s-registryctl" (include "harbor.fullname" .) -}} +{{- end -}} + +{{- define "harbor.chartmuseum" -}} + {{- printf "%s-chartmuseum" (include "harbor.fullname" .) -}} +{{- end -}} + +{{- define "harbor.database" -}} + {{- printf "%s-database" (include "harbor.fullname" .) -}} +{{- end -}} + +{{- define "harbor.trivy" -}} + {{- printf "%s-trivy" (include "harbor.fullname" .) -}} +{{- end -}} + +{{- define "harbor.notary-server" -}} + {{- printf "%s-notary-server" (include "harbor.fullname" .) -}} +{{- end -}} + +{{- define "harbor.notary-signer" -}} + {{- printf "%s-notary-signer" (include "harbor.fullname" .) -}} +{{- end -}} + +{{- define "harbor.nginx" -}} + {{- printf "%s-nginx" (include "harbor.fullname" .) -}} +{{- end -}} + +{{- define "harbor.exporter" -}} + {{- printf "%s-exporter" (include "harbor.fullname" .) -}} +{{- end -}} + +{{- define "harbor.ingress" -}} + {{- printf "%s-ingress" (include "harbor.fullname" .) -}} +{{- end -}} + +{{- define "harbor.ingress-notary" -}} + {{- printf "%s-ingress-notary" (include "harbor.fullname" .) -}} +{{- end -}} + +{{- define "harbor.noProxy" -}} + {{- printf "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s" (include "harbor.core" .) (include "harbor.jobservice" .) (include "harbor.database" .) (include "harbor.chartmuseum" .) (include "harbor.notary-server" .) (include "harbor.notary-signer" .) (include "harbor.registry" .) (include "harbor.portal" .) (include "harbor.trivy" .) (include "harbor.exporter" .) .Values.proxy.noProxy -}} +{{- end -}} + +{{- define "harbor.caBundleVolume" -}} +- name: ca-bundle-certs + secret: + secretName: {{ .Values.caBundleSecretName }} +{{- end -}} + +{{- define "harbor.caBundleVolumeMount" -}} +- name: ca-bundle-certs + mountPath: /harbor_cust_cert/custom-ca.crt + subPath: ca.crt +{{- end -}} + +{{/* scheme for all components except notary because it only support http mode */}} +{{- define "harbor.component.scheme" -}} + {{- if .Values.internalTLS.enabled -}} + {{- printf "https" -}} + {{- else -}} + {{- printf "http" -}} + {{- end -}} +{{- end -}} + +{{/* chartmuseum component container port */}} +{{- define "harbor.chartmuseum.containerPort" -}} + {{- if .Values.internalTLS.enabled -}} + {{- printf "9443" -}} + {{- else -}} + {{- printf "9999" -}} + {{- end -}} +{{- end -}} + +{{/* chartmuseum component service port */}} +{{- define "harbor.chartmuseum.servicePort" -}} + {{- if .Values.internalTLS.enabled -}} + {{- printf "443" -}} + {{- else -}} + {{- printf "80" -}} + {{- end -}} +{{- end -}} + +{{/* core component container port */}} +{{- define "harbor.core.containerPort" -}} + {{- if .Values.internalTLS.enabled -}} + {{- printf "8443" -}} + {{- else -}} + {{- printf "8080" -}} + {{- end -}} +{{- end -}} + +{{/* core component service port */}} +{{- define "harbor.core.servicePort" -}} + {{- if .Values.internalTLS.enabled -}} + {{- printf "443" -}} + {{- else -}} + {{- printf "80" -}} + {{- end -}} +{{- end -}} + +{{/* jobservice component container port */}} +{{- define "harbor.jobservice.containerPort" -}} + {{- if .Values.internalTLS.enabled -}} + {{- printf "8443" -}} + {{- else -}} + {{- printf "8080" -}} + {{- end -}} +{{- end -}} + +{{/* jobservice component service port */}} +{{- define "harbor.jobservice.servicePort" -}} + {{- if .Values.internalTLS.enabled -}} + {{- printf "443" -}} + {{- else -}} + {{- printf "80" -}} + {{- end -}} +{{- end -}} + +{{/* portal component container port */}} +{{- define "harbor.portal.containerPort" -}} + {{- if .Values.internalTLS.enabled -}} + {{- printf "8443" -}} + {{- else -}} + {{- printf "8080" -}} + {{- end -}} +{{- end -}} + +{{/* portal component service port */}} +{{- define "harbor.portal.servicePort" -}} + {{- if .Values.internalTLS.enabled -}} + {{- printf "443" -}} + {{- else -}} + {{- printf "80" -}} + {{- end -}} +{{- end -}} + +{{/* registry component container port */}} +{{- define "harbor.registry.containerPort" -}} + {{- if .Values.internalTLS.enabled -}} + {{- printf "5443" -}} + {{- else -}} + {{- printf "5000" -}} + {{- end -}} +{{- end -}} + +{{/* registry component service port */}} +{{- define "harbor.registry.servicePort" -}} + {{- if .Values.internalTLS.enabled -}} + {{- printf "5443" -}} + {{- else -}} + {{- printf "5000" -}} + {{- end -}} +{{- end -}} + +{{/* registryctl component container port */}} +{{- define "harbor.registryctl.containerPort" -}} + {{- if .Values.internalTLS.enabled -}} + {{- printf "8443" -}} + {{- else -}} + {{- printf "8080" -}} + {{- end -}} +{{- end -}} + +{{/* registryctl component service port */}} +{{- define "harbor.registryctl.servicePort" -}} + {{- if .Values.internalTLS.enabled -}} + {{- printf "8443" -}} + {{- else -}} + {{- printf "8080" -}} + {{- end -}} +{{- end -}} + +{{/* trivy component container port */}} +{{- define "harbor.trivy.containerPort" -}} + {{- if .Values.internalTLS.enabled -}} + {{- printf "8443" -}} + {{- else -}} + {{- printf "8080" -}} + {{- end -}} +{{- end -}} + +{{/* trivy component service port */}} +{{- define "harbor.trivy.servicePort" -}} + {{- if .Values.internalTLS.enabled -}} + {{- printf "8443" -}} + {{- else -}} + {{- printf "8080" -}} + {{- end -}} +{{- end -}} + +{{/* CORE_URL */}} +{{/* port is included in this url as a workaround for issue https://github.com/aquasecurity/harbor-scanner-trivy/issues/108 */}} +{{- define "harbor.coreURL" -}} + {{- printf "%s://%s:%s" (include "harbor.component.scheme" .) (include "harbor.core" .) (include "harbor.core.servicePort" .) -}} +{{- end -}} + +{{/* JOBSERVICE_URL */}} +{{- define "harbor.jobserviceURL" -}} + {{- printf "%s://%s-jobservice" (include "harbor.component.scheme" .) (include "harbor.fullname" .) -}} +{{- end -}} + +{{/* PORTAL_URL */}} +{{- define "harbor.portalURL" -}} + {{- printf "%s://%s" (include "harbor.component.scheme" .) (include "harbor.portal" .) -}} +{{- end -}} + +{{/* REGISTRY_URL */}} +{{- define "harbor.registryURL" -}} + {{- printf "%s://%s:%s" (include "harbor.component.scheme" .) (include "harbor.registry" .) (include "harbor.registry.servicePort" .) -}} +{{- end -}} + +{{/* REGISTRY_CONTROLLER_URL */}} +{{- define "harbor.registryControllerURL" -}} + {{- printf "%s://%s:%s" (include "harbor.component.scheme" .) (include "harbor.registry" .) (include "harbor.registryctl.servicePort" .) -}} +{{- end -}} + +{{/* TOKEN_SERVICE_URL */}} +{{- define "harbor.tokenServiceURL" -}} + {{- printf "%s/service/token" (include "harbor.coreURL" .) -}} +{{- end -}} + +{{/* TRIVY_ADAPTER_URL */}} +{{- define "harbor.trivyAdapterURL" -}} + {{- printf "%s://%s:%s" (include "harbor.component.scheme" .) (include "harbor.trivy" .) (include "harbor.trivy.servicePort" .) -}} +{{- end -}} + +{{- define "harbor.internalTLS.chartmuseum.secretName" -}} + {{- if eq .Values.internalTLS.certSource "secret" -}} + {{- .Values.internalTLS.chartmuseum.secretName -}} + {{- else -}} + {{- printf "%s-chartmuseum-internal-tls" (include "harbor.fullname" .) -}} + {{- end -}} +{{- end -}} + +{{- define "harbor.internalTLS.core.secretName" -}} + {{- if eq .Values.internalTLS.certSource "secret" -}} + {{- .Values.internalTLS.core.secretName -}} + {{- else -}} + {{- printf "%s-core-internal-tls" (include "harbor.fullname" .) -}} + {{- end -}} +{{- end -}} + +{{- define "harbor.internalTLS.jobservice.secretName" -}} + {{- if eq .Values.internalTLS.certSource "secret" -}} + {{- .Values.internalTLS.jobservice.secretName -}} + {{- else -}} + {{- printf "%s-jobservice-internal-tls" (include "harbor.fullname" .) -}} + {{- end -}} +{{- end -}} + +{{- define "harbor.internalTLS.portal.secretName" -}} + {{- if eq .Values.internalTLS.certSource "secret" -}} + {{- .Values.internalTLS.portal.secretName -}} + {{- else -}} + {{- printf "%s-portal-internal-tls" (include "harbor.fullname" .) -}} + {{- end -}} +{{- end -}} + +{{- define "harbor.internalTLS.registry.secretName" -}} + {{- if eq .Values.internalTLS.certSource "secret" -}} + {{- .Values.internalTLS.registry.secretName -}} + {{- else -}} + {{- printf "%s-registry-internal-tls" (include "harbor.fullname" .) -}} + {{- end -}} +{{- end -}} + +{{- define "harbor.internalTLS.trivy.secretName" -}} + {{- if eq .Values.internalTLS.certSource "secret" -}} + {{- .Values.internalTLS.trivy.secretName -}} + {{- else -}} + {{- printf "%s-trivy-internal-tls" (include "harbor.fullname" .) -}} + {{- end -}} +{{- end -}} + +{{- define "harbor.tlsCoreSecretForIngress" -}} + {{- if eq .Values.expose.tls.certSource "none" -}} + {{- printf "" -}} + {{- else if eq .Values.expose.tls.certSource "secret" -}} + {{- .Values.expose.tls.secret.secretName -}} + {{- else -}} + {{- include "harbor.ingress" . -}} + {{- end -}} +{{- end -}} + +{{- define "harbor.tlsNotarySecretForIngress" -}} + {{- if eq .Values.expose.tls.certSource "none" -}} + {{- printf "" -}} + {{- else if eq .Values.expose.tls.certSource "secret" -}} + {{- .Values.expose.tls.secret.notarySecretName -}} + {{- else -}} + {{- include "harbor.ingress" . -}} + {{- end -}} +{{- end -}} + +{{- define "harbor.tlsSecretForNginx" -}} + {{- if eq .Values.expose.tls.certSource "secret" -}} + {{- .Values.expose.tls.secret.secretName -}} + {{- else -}} + {{- include "harbor.nginx" . -}} + {{- end -}} +{{- end -}} + +{{- define "harbor.metricsPortName" -}} + {{- if .Values.internalTLS.enabled }} + {{- printf "https-metrics" -}} + {{- else -}} + {{- printf "http-metrics" -}} + {{- end -}} +{{- end -}} + +{{- define "harbor.traceEnvs" -}} + TRACE_ENABLED: "{{ .Values.trace.enabled }}" + TRACE_SAMPLE_RATE: "{{ .Values.trace.sample_rate }}" + TRACE_NAMESPACE: "{{ .Values.trace.namespace }}" + {{- if .Values.trace.attributes }} + TRACE_ATTRIBUTES: "{{ .Values.trace.attributes | toJson }}" + {{- end }} + {{- if eq .Values.trace.provider "jaeger" }} + TRACE_JAEGER_ENDPOINT: "{{ .Values.trace.jaeger.endpoint }}" + TRACE_JAEGER_USERNAME: "{{ .Values.trace.jaeger.username }}" + TRACE_JAEGER_AGENT_HOSTNAME: "{{ .Values.trace.jaeger.agent_host }}" + TRACE_JAEGER_AGENT_PORT: "{{ .Values.trace.jaeger.agent_port }}" + {{- else }} + TRACE_OTEL_ENDPOINT: "{{ .Values.trace.otel.endpoint }}" + TRACE_OTEL_URL_PATH: "{{ .Values.trace.otel.url_path }}" + TRACE_OTEL_COMPRESSION: "{{ .Values.trace.otel.compression }}" + TRACE_OTEL_INSECURE: "{{ .Values.trace.otel.insecure }}" + TRACE_OTEL_TIMEOUT: "{{ .Values.trace.otel.timeout }}" + {{- end }} +{{- end -}} + +{{- define "harbor.traceEnvsForCore" -}} + {{- if .Values.trace.enabled }} + TRACE_SERVICE_NAME: "harbor-core" + {{ include "harbor.traceEnvs" . }} + {{- end }} +{{- end -}} + +{{- define "harbor.traceEnvsForJobservice" -}} + {{- if .Values.trace.enabled }} + TRACE_SERVICE_NAME: "harbor-jobservice" + {{ include "harbor.traceEnvs" . }} + {{- end }} +{{- end -}} + +{{- define "harbor.traceEnvsForRegistryCtl" -}} + {{- if .Values.trace.enabled }} + TRACE_SERVICE_NAME: "harbor-registryctl" + {{ include "harbor.traceEnvs" . }} + {{- end }} +{{- end -}} + +{{- define "harbor.traceJaegerPassword" -}} + {{- if and .Values.trace.enabled (eq .Values.trace.provider "jaeger") }} + TRACE_JAEGER_PASSWORD: "{{ .Values.trace.jaeger.password | default "" | b64enc }}" + {{- end }} +{{- end -}} + +{{/* Allow KubeVersion to be overridden. */}} +{{- define "harbor.ingress.kubeVersion" -}} + {{- default .Capabilities.KubeVersion.Version .Values.expose.ingress.kubeVersionOverride -}} +{{- end -}} \ No newline at end of file diff --git a/ResearchOps/harbor/templates/chartmuseum/chartmuseum-cm.yaml b/ResearchOps/harbor/templates/chartmuseum/chartmuseum-cm.yaml new file mode 100644 index 0000000..8d0a5f8 --- /dev/null +++ b/ResearchOps/harbor/templates/chartmuseum/chartmuseum-cm.yaml @@ -0,0 +1,113 @@ +{{- if .Values.chartmuseum.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: "{{ template "harbor.chartmuseum" . }}" + labels: +{{ include "harbor.labels" . | indent 4 }} +data: + PORT: "{{ template "harbor.chartmuseum.containerPort" . }}" +{{- if .Values.internalTLS.enabled }} + TLS_CERT: "/etc/harbor/ssl/chartmuseum/tls.crt" + TLS_KEY: "/etc/harbor/ssl/chartmuseum/tls.key" +{{- end }} + {{- if eq "redis" (include "harbor.redis.scheme" .) }} + CACHE: "redis" + {{- else }} + CACHE: "redis_sentinel" + CACHE_REDIS_MASTERNAME: "{{ template "harbor.redis.masterSet" . }}" + {{- end }} + CACHE_REDIS_ADDR: "{{ template "harbor.redis.addr" . }}" + CACHE_REDIS_DB: "{{ template "harbor.redis.dbForChartmuseum" . }}" + BASIC_AUTH_USER: "chart_controller" +{{- if .Values.chartmuseum.absoluteUrl }} + CHART_URL: {{ .Values.externalURL }}/chartrepo +{{- end }} + DEPTH: "1" +{{- if eq .Values.logLevel "debug" }} + DEBUG: "true" +{{- else }} + DEBUG: "false" +{{- end }} + LOG_JSON: "true" + DISABLE_METRICS: "false" + DISABLE_API: "false" + DISABLE_STATEFILES: "false" + ALLOW_OVERWRITE: "true" + AUTH_ANONYMOUS_GET: "false" + CONTEXT_PATH: "" + INDEX_LIMIT: {{ .Values.chartmuseum.indexLimit | quote}} + MAX_STORAGE_OBJECTS: "0" + MAX_UPLOAD_SIZE: "20971520" + CHART_POST_FORM_FIELD_NAME: "chart" + PROV_POST_FORM_FIELD_NAME: "prov" +{{- $storage := .Values.persistence.imageChartStorage }} +{{- $storageType := $storage.type }} +{{- if eq $storageType "filesystem" }} + STORAGE: "local" + STORAGE_LOCAL_ROOTDIR: "/chart_storage" +{{- else if eq $storageType "azure" }} + STORAGE: "microsoft" + STORAGE_MICROSOFT_CONTAINER: {{ $storage.azure.container }} + AZURE_STORAGE_ACCOUNT: {{ $storage.azure.accountname }} + AZURE_BASE_URL: {{ $storage.azure.realm }} + STORAGE_MICROSOFT_PREFIX: "/azure/harbor/charts" +{{- else if eq $storageType "gcs" }} + STORAGE: "google" + STORAGE_GOOGLE_BUCKET: {{ $storage.gcs.bucket }} + GOOGLE_APPLICATION_CREDENTIALS: /etc/chartmuseum/gcs-key.json + {{- if $storage.gcs.rootdirectory }} + STORAGE_GOOGLE_PREFIX: {{ $storage.gcs.rootdirectory }} + {{- end }} +{{- else if eq $storageType "s3" }} + STORAGE: "amazon" + STORAGE_AMAZON_BUCKET: {{ $storage.s3.bucket }} + {{- if $storage.s3.rootdirectory }} + STORAGE_AMAZON_PREFIX: {{ $storage.s3.rootdirectory }} + {{- end }} + STORAGE_AMAZON_REGION: {{ $storage.s3.region }} + {{- if $storage.s3.regionendpoint }} + STORAGE_AMAZON_ENDPOINT: {{ $storage.s3.regionendpoint }} + {{- end }} + {{- if $storage.s3.accesskey }} + AWS_ACCESS_KEY_ID: {{ $storage.s3.accesskey }} + {{- end }} + {{- if $storage.s3.keyid }} + STORAGE_AMAZON_SSE: aws:kms + {{- end }} +{{- else if eq $storageType "swift" }} + STORAGE: "openstack" + STORAGE_OPENSTACK_CONTAINER: {{ $storage.swift.container }} + {{- if $storage.swift.prefix }} + STORAGE_OPENSTACK_PREFIX: {{ $storage.swift.prefix }} + {{- end }} + {{- if $storage.swift.region }} + STORAGE_OPENSTACK_REGION: {{ $storage.swift.region }} + {{- end }} + OS_AUTH_URL: {{ $storage.swift.authurl }} + OS_USERNAME: {{ $storage.swift.username }} + {{- if $storage.swift.tenantid }} + OS_PROJECT_ID: {{ $storage.swift.tenantid }} + {{- end }} + {{- if $storage.swift.tenant }} + OS_PROJECT_NAME: {{ $storage.swift.tenant }} + {{- end }} + {{- if $storage.swift.domainid }} + OS_DOMAIN_ID: {{ $storage.swift.domainid }} + {{- end }} + {{- if $storage.swift.domain }} + OS_DOMAIN_NAME: {{ $storage.swift.domain }} + {{- end }} +{{- else if eq $storageType "oss" }} + STORAGE: "alibaba" + STORAGE_ALIBABA_BUCKET: {{ $storage.oss.bucket }} + {{- if $storage.oss.rootdirectory }} + STORAGE_ALIBABA_PREFIX: {{ $storage.oss.rootdirectory }} + {{- end }} + {{- if $storage.oss.endpoint }} + STORAGE_ALIBABA_ENDPOINT: {{ $storage.oss.endpoint }} + {{- end }} + ALIBABA_CLOUD_ACCESS_KEY_ID: {{ $storage.oss.accesskeyid }} +{{- end }} + STORAGE_TIMESTAMP_TOLERANCE: 1s +{{- end }} diff --git a/ResearchOps/harbor/templates/chartmuseum/chartmuseum-dpl.yaml b/ResearchOps/harbor/templates/chartmuseum/chartmuseum-dpl.yaml new file mode 100644 index 0000000..d73a73a --- /dev/null +++ b/ResearchOps/harbor/templates/chartmuseum/chartmuseum-dpl.yaml @@ -0,0 +1,171 @@ +{{- if .Values.chartmuseum.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: "{{ template "harbor.chartmuseum" . }}" + labels: +{{ include "harbor.labels" . | indent 4 }} + component: chartmuseum +spec: + replicas: {{ .Values.chartmuseum.replicas }} + revisionHistoryLimit: {{ .Values.chartmuseum.revisionHistoryLimit }} + strategy: + type: {{ .Values.updateStrategy.type }} + {{- if eq .Values.updateStrategy.type "Recreate" }} + rollingUpdate: null + {{- end }} + selector: + matchLabels: +{{ include "harbor.matchLabels" . | indent 6 }} + component: chartmuseum + template: + metadata: + labels: +{{ include "harbor.labels" . | indent 8 }} + component: chartmuseum + annotations: + checksum/configmap: {{ include (print $.Template.BasePath "/chartmuseum/chartmuseum-cm.yaml") . | sha256sum }} + checksum/secret: {{ include (print $.Template.BasePath "/chartmuseum/chartmuseum-secret.yaml") . | sha256sum }} + checksum/secret-core: {{ include (print $.Template.BasePath "/core/core-secret.yaml") . | sha256sum }} +{{- if and .Values.internalTLS.enabled (eq .Values.internalTLS.certSource "auto") }} + checksum/tls: {{ include (print $.Template.BasePath "/internal/auto-tls.yaml") . | sha256sum }} +{{- else if and .Values.internalTLS.enabled (eq .Values.internalTLS.certSource "manual") }} + checksum/tls: {{ include (print $.Template.BasePath "/chartmuseum/chartmuseum-tls.yaml") . | sha256sum }} +{{- end }} +{{- if .Values.chartmuseum.podAnnotations }} +{{ toYaml .Values.chartmuseum.podAnnotations | indent 8 }} +{{- end }} + spec: + securityContext: + runAsUser: 10000 + fsGroup: 10000 +{{- if .Values.chartmuseum.serviceAccountName }} + serviceAccountName: {{ .Values.chartmuseum.serviceAccountName }} +{{- end -}} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + automountServiceAccountToken: {{ .Values.chartmuseum.automountServiceAccountToken | default false }} + containers: + - name: chartmuseum + image: {{ .Values.chartmuseum.image.repository }}:{{ .Values.chartmuseum.image.tag }} + imagePullPolicy: {{ .Values.imagePullPolicy }} + livenessProbe: + httpGet: + path: /health + scheme: {{ include "harbor.component.scheme" . | upper }} + port: {{ template "harbor.chartmuseum.containerPort" . }} + initialDelaySeconds: 300 + periodSeconds: 10 + readinessProbe: + httpGet: + path: /health + scheme: {{ include "harbor.component.scheme" . | upper }} + port: {{ template "harbor.chartmuseum.containerPort" . }} + initialDelaySeconds: 1 + periodSeconds: 10 +{{- if .Values.chartmuseum.resources }} + resources: +{{ toYaml .Values.chartmuseum.resources | indent 10 }} +{{- end }} + envFrom: + - configMapRef: + name: "{{ template "harbor.chartmuseum" . }}" + - secretRef: + name: "{{ template "harbor.chartmuseum" . }}" + env: + {{- if has "chartmuseum" .Values.proxy.components }} + - name: HTTP_PROXY + value: "{{ .Values.proxy.httpProxy }}" + - name: HTTPS_PROXY + value: "{{ .Values.proxy.httpsProxy }}" + - name: NO_PROXY + value: "{{ template "harbor.noProxy" . }}" + {{- end }} + {{- if .Values.internalTLS.enabled }} + - name: INTERNAL_TLS_ENABLED + value: "true" + - name: INTERNAL_TLS_KEY_PATH + value: /etc/harbor/ssl/chartmuseum/tls.key + - name: INTERNAL_TLS_CERT_PATH + value: /etc/harbor/ssl/chartmuseum/tls.crt + - name: INTERNAL_TLS_TRUST_CA_PATH + value: /etc/harbor/ssl/chartmuseum/ca.crt + {{- end }} + - name: BASIC_AUTH_PASS + valueFrom: + secretKeyRef: + name: {{ template "harbor.core" . }} + key: secret + - # Needed to make AWS' client connect correctly (see https://github.com/helm/chartmuseum/issues/280) + name: AWS_SDK_LOAD_CONFIG + value: "1" + ports: + - containerPort: {{ template "harbor.chartmuseum.containerPort" . }} + volumeMounts: + - name: chartmuseum-data + mountPath: /chart_storage + subPath: {{ .Values.persistence.persistentVolumeClaim.chartmuseum.subPath }} + {{- if .Values.internalTLS.enabled }} + - name: chart-internal-certs + mountPath: /etc/harbor/ssl/chartmuseum + {{- end }} + {{- if and .Values.persistence.enabled (eq .Values.persistence.imageChartStorage.type "gcs") }} + - name: gcs-key + mountPath: /etc/chartmuseum/gcs-key.json + subPath: gcs-key.json + {{- end }} + {{- if .Values.persistence.imageChartStorage.caBundleSecretName }} + - name: storage-service-ca + mountPath: /harbor_cust_cert/custom-ca-bundle.crt + subPath: ca.crt + {{- end }} + {{- if .Values.caBundleSecretName }} +{{ include "harbor.caBundleVolumeMount" . | indent 8 }} + {{- end }} + volumes: + - name: chartmuseum-data + {{- if and .Values.persistence.enabled (eq .Values.persistence.imageChartStorage.type "filesystem") }} + persistentVolumeClaim: + claimName: {{ .Values.persistence.persistentVolumeClaim.chartmuseum.existingClaim | default (include "harbor.chartmuseum" .) }} + {{- else }} + emptyDir: {} + {{- end }} + {{- if .Values.internalTLS.enabled }} + - name: chart-internal-certs + secret: + secretName: {{ template "harbor.internalTLS.chartmuseum.secretName" . }} + {{- end }} + {{- if and .Values.persistence.enabled (eq .Values.persistence.imageChartStorage.type "gcs") }} + - name: gcs-key + secret: + secretName: {{ template "harbor.registry" . }} + items: + - key: GCS_KEY_DATA + path: gcs-key.json + {{- end }} + {{- if .Values.persistence.imageChartStorage.caBundleSecretName }} + - name: storage-service-ca + secret: + secretName: {{ .Values.persistence.imageChartStorage.caBundleSecretName }} + {{- end }} + {{- if .Values.caBundleSecretName }} +{{ include "harbor.caBundleVolume" . | indent 6 }} + {{- end }} + {{- with .Values.chartmuseum.nodeSelector }} + nodeSelector: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.chartmuseum.affinity }} + affinity: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.chartmuseum.tolerations }} + tolerations: +{{ toYaml . | indent 8 }} + {{- end }} + {{- if .Values.chartmuseum.priorityClassName }} + priorityClassName: {{ .Values.chartmuseum.priorityClassName }} + {{- end }} +{{- end }} diff --git a/ResearchOps/harbor/templates/chartmuseum/chartmuseum-pvc.yaml b/ResearchOps/harbor/templates/chartmuseum/chartmuseum-pvc.yaml new file mode 100644 index 0000000..fd62ac3 --- /dev/null +++ b/ResearchOps/harbor/templates/chartmuseum/chartmuseum-pvc.yaml @@ -0,0 +1,35 @@ +{{- if .Values.chartmuseum.enabled }} +{{- $persistence := .Values.persistence -}} +{{- if $persistence.enabled }} +{{- $chartmuseum := $persistence.persistentVolumeClaim.chartmuseum -}} +{{- if and (not $chartmuseum.existingClaim) (eq $persistence.imageChartStorage.type "filesystem") }} +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: {{ template "harbor.chartmuseum" . }} + annotations: + {{- range $key, $value := $chartmuseum.annotations }} + {{ $key }}: {{ $value | quote }} + {{- end }} + {{- if eq .Values.persistence.resourcePolicy "keep" }} + helm.sh/resource-policy: keep + {{- end }} + labels: +{{ include "harbor.labels" . | indent 4 }} + component: chartmuseum +spec: + accessModes: + - {{ $chartmuseum.accessMode }} + resources: + requests: + storage: {{ $chartmuseum.size }} + {{- if $chartmuseum.storageClass }} + {{- if eq "-" $chartmuseum.storageClass }} + storageClassName: "" + {{- else }} + storageClassName: {{ $chartmuseum.storageClass }} + {{- end }} + {{- end }} +{{- end }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/ResearchOps/harbor/templates/chartmuseum/chartmuseum-secret.yaml b/ResearchOps/harbor/templates/chartmuseum/chartmuseum-secret.yaml new file mode 100644 index 0000000..eefdf79 --- /dev/null +++ b/ResearchOps/harbor/templates/chartmuseum/chartmuseum-secret.yaml @@ -0,0 +1,26 @@ +{{- if .Values.chartmuseum.enabled }} +apiVersion: v1 +kind: Secret +metadata: + name: "{{ template "harbor.chartmuseum" . }}" + labels: +{{ include "harbor.labels" . | indent 4 }} +type: Opaque +data: + CACHE_REDIS_PASSWORD: {{ include "harbor.redis.password" . | b64enc | quote }} +{{- $storage := .Values.persistence.imageChartStorage }} +{{- $storageType := $storage.type }} +{{- if eq $storageType "azure" }} + AZURE_STORAGE_ACCESS_KEY: {{ $storage.azure.accountkey | b64enc | quote }} +{{- else if eq $storageType "gcs" }} + # TODO support the keyfile of gcs +{{- else if eq $storageType "s3" }} + {{- if $storage.s3.secretkey }} + AWS_SECRET_ACCESS_KEY: {{ $storage.s3.secretkey | b64enc | quote }} + {{- end }} +{{- else if eq $storageType "swift" }} + OS_PASSWORD: {{ $storage.swift.password | b64enc | quote }} +{{- else if eq $storageType "oss" }} + ALIBABA_CLOUD_ACCESS_KEY_SECRET: {{ $storage.oss.accesskeysecret | b64enc | quote }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/ResearchOps/harbor/templates/chartmuseum/chartmuseum-svc.yaml b/ResearchOps/harbor/templates/chartmuseum/chartmuseum-svc.yaml new file mode 100644 index 0000000..df58475 --- /dev/null +++ b/ResearchOps/harbor/templates/chartmuseum/chartmuseum-svc.yaml @@ -0,0 +1,15 @@ +{{- if .Values.chartmuseum.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: "{{ template "harbor.chartmuseum" . }}" + labels: +{{ include "harbor.labels" . | indent 4 }} +spec: + ports: + - port: {{ template "harbor.chartmuseum.servicePort" . }} + targetPort: {{ template "harbor.chartmuseum.containerPort" . }} + selector: +{{ include "harbor.matchLabels" . | indent 4 }} + component: chartmuseum +{{- end }} \ No newline at end of file diff --git a/ResearchOps/harbor/templates/chartmuseum/chartmuseum-tls.yaml b/ResearchOps/harbor/templates/chartmuseum/chartmuseum-tls.yaml new file mode 100644 index 0000000..cda17c3 --- /dev/null +++ b/ResearchOps/harbor/templates/chartmuseum/chartmuseum-tls.yaml @@ -0,0 +1,15 @@ +{{- if and .Values.chartmuseum.enabled .Values.internalTLS.enabled }} +{{- if eq .Values.internalTLS.certSource "manual" }} +apiVersion: v1 +kind: Secret +metadata: + name: "{{ template "harbor.internalTLS.chartmuseum.secretName" . }}" + labels: +{{ include "harbor.labels" . | indent 4 }} +type: kubernetes.io/tls +data: + tls.ca: {{ (required "The \"internalTLS.trustCa\" is required!" .Values.internalTLS.trustCa) | b64enc | quote }} + tls.crt: {{ (required "The \"internalTLS.chartmuseum.crt\" is required!" .Values.internalTLS.chartmuseum.crt) | b64enc | quote }} + tls.key: {{ (required "The \"internalTLS.chartmuseum.key\" is required!" .Values.internalTLS.chartmuseum.key) | b64enc | quote }} +{{- end }} +{{- end }} diff --git a/ResearchOps/harbor/templates/core/core-cm.yaml b/ResearchOps/harbor/templates/core/core-cm.yaml new file mode 100644 index 0000000..41226fb --- /dev/null +++ b/ResearchOps/harbor/templates/core/core-cm.yaml @@ -0,0 +1,70 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "harbor.core" . }} + labels: +{{ include "harbor.labels" . | indent 4 }} +data: + app.conf: |+ + appname = Harbor + runmode = prod + enablegzip = true + + [prod] + httpport = {{ ternary "8443" "8080" .Values.internalTLS.enabled }} + PORT: "{{ ternary "8443" "8080" .Values.internalTLS.enabled }}" + DATABASE_TYPE: "postgresql" + POSTGRESQL_HOST: "{{ template "harbor.database.host" . }}" + POSTGRESQL_PORT: "{{ template "harbor.database.port" . }}" + POSTGRESQL_USERNAME: "{{ template "harbor.database.username" . }}" + POSTGRESQL_DATABASE: "{{ template "harbor.database.coreDatabase" . }}" + POSTGRESQL_SSLMODE: "{{ template "harbor.database.sslmode" . }}" + POSTGRESQL_MAX_IDLE_CONNS: "{{ .Values.database.maxIdleConns }}" + POSTGRESQL_MAX_OPEN_CONNS: "{{ .Values.database.maxOpenConns }}" + EXT_ENDPOINT: "{{ .Values.externalURL }}" + CORE_URL: "{{ template "harbor.coreURL" . }}" + JOBSERVICE_URL: "{{ template "harbor.jobserviceURL" . }}" + REGISTRY_URL: "{{ template "harbor.registryURL" . }}" + TOKEN_SERVICE_URL: "{{ template "harbor.tokenServiceURL" . }}" + WITH_NOTARY: "{{ .Values.notary.enabled }}" + NOTARY_URL: "http://{{ template "harbor.notary-server" . }}:4443" + CORE_LOCAL_URL: "{{ ternary "https://127.0.0.1:8443" "http://127.0.0.1:8080" .Values.internalTLS.enabled }}" + WITH_TRIVY: {{ .Values.trivy.enabled | quote }} + TRIVY_ADAPTER_URL: "{{ template "harbor.trivyAdapterURL" . }}" + REGISTRY_STORAGE_PROVIDER_NAME: "{{ .Values.persistence.imageChartStorage.type }}" + WITH_CHARTMUSEUM: "{{ .Values.chartmuseum.enabled }}" + CHART_REPOSITORY_URL: "{{ template "harbor.component.scheme" . }}://{{ template "harbor.chartmuseum" . }}" + LOG_LEVEL: "{{ .Values.logLevel }}" + CONFIG_PATH: "/etc/core/app.conf" + CHART_CACHE_DRIVER: "redis" + _REDIS_URL_CORE: "{{ template "harbor.redis.urlForCore" . }}" + _REDIS_URL_REG: "{{ template "harbor.redis.urlForRegistry" . }}" + PORTAL_URL: "{{ template "harbor.portalURL" . }}" + REGISTRY_CONTROLLER_URL: "{{ template "harbor.registryControllerURL" . }}" + REGISTRY_CREDENTIAL_USERNAME: "{{ .Values.registry.credentials.username }}" + {{- if .Values.uaaSecretName }} + UAA_CA_ROOT: "/etc/core/auth-ca/auth-ca.crt" + {{- end }} + {{- if has "core" .Values.proxy.components }} + HTTP_PROXY: "{{ .Values.proxy.httpProxy }}" + HTTPS_PROXY: "{{ .Values.proxy.httpsProxy }}" + NO_PROXY: "{{ template "harbor.noProxy" . }}" + {{- end }} + PERMITTED_REGISTRY_TYPES_FOR_PROXY_CACHE: "docker-hub,harbor,azure-acr,aws-ecr,google-gcr,quay,docker-registry" + {{- if .Values.metrics.enabled}} + METRIC_ENABLE: "true" + METRIC_PATH: "{{ .Values.metrics.core.path }}" + METRIC_PORT: "{{ .Values.metrics.core.port }}" + METRIC_NAMESPACE: harbor + METRIC_SUBSYSTEM: core + {{- end }} + + {{- if hasKey .Values.core "gcTimeWindowHours" }} + #make the GC time window configurable for testing + GC_TIME_WINDOW_HOURS: "{{ .Values.core.gcTimeWindowHours }}" + {{- end }} + {{- template "harbor.traceEnvsForCore" . }} + + {{- if .Values.core.artifactPullAsyncFlushDuration | quote }} + ARTIFACT_PULL_ASYNC_FLUSH_DURATION: {{ .Values.core.artifactPullAsyncFlushDuration }} + {{- end }} \ No newline at end of file diff --git a/ResearchOps/harbor/templates/core/core-dpl.yaml b/ResearchOps/harbor/templates/core/core-dpl.yaml new file mode 100644 index 0000000..b94b789 --- /dev/null +++ b/ResearchOps/harbor/templates/core/core-dpl.yaml @@ -0,0 +1,196 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "harbor.core" . }} + labels: +{{ include "harbor.labels" . | indent 4 }} + component: core +spec: + replicas: {{ .Values.core.replicas }} + revisionHistoryLimit: {{ .Values.core.revisionHistoryLimit }} + selector: + matchLabels: +{{ include "harbor.matchLabels" . | indent 6 }} + component: core + template: + metadata: + labels: +{{ include "harbor.matchLabels" . | indent 8 }} + component: core + annotations: + checksum/configmap: {{ include (print $.Template.BasePath "/core/core-cm.yaml") . | sha256sum }} + checksum/secret: {{ include (print $.Template.BasePath "/core/core-secret.yaml") . | sha256sum }} + checksum/secret-jobservice: {{ include (print $.Template.BasePath "/jobservice/jobservice-secrets.yaml") . | sha256sum }} +{{- if and .Values.internalTLS.enabled (eq .Values.internalTLS.certSource "auto") }} + checksum/tls: {{ include (print $.Template.BasePath "/internal/auto-tls.yaml") . | sha256sum }} +{{- else if and .Values.internalTLS.enabled (eq .Values.internalTLS.certSource "manual") }} + checksum/tls: {{ include (print $.Template.BasePath "/core/core-tls.yaml") . | sha256sum }} +{{- end }} +{{- if .Values.core.podAnnotations }} +{{ toYaml .Values.core.podAnnotations | indent 8 }} +{{- end }} + spec: + securityContext: + runAsUser: 10000 + fsGroup: 10000 +{{- if .Values.core.serviceAccountName }} + serviceAccountName: {{ .Values.core.serviceAccountName }} +{{- end -}} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + automountServiceAccountToken: {{ .Values.core.automountServiceAccountToken | default false }} + terminationGracePeriodSeconds: 120 + containers: + - name: core + image: {{ .Values.core.image.repository }}:{{ .Values.core.image.tag }} + imagePullPolicy: {{ .Values.imagePullPolicy }} + {{- if .Values.core.startupProbe.enabled }} + startupProbe: + httpGet: + path: /api/v2.0/ping + scheme: {{ include "harbor.component.scheme" . | upper }} + port: {{ template "harbor.core.containerPort" . }} + failureThreshold: 360 + initialDelaySeconds: {{ .Values.core.startupProbe.initialDelaySeconds }} + periodSeconds: 10 + {{- end }} + livenessProbe: + httpGet: + path: /api/v2.0/ping + scheme: {{ include "harbor.component.scheme" . | upper }} + port: {{ template "harbor.core.containerPort" . }} + failureThreshold: 2 + periodSeconds: 10 + readinessProbe: + httpGet: + path: /api/v2.0/ping + scheme: {{ include "harbor.component.scheme" . | upper }} + port: {{ template "harbor.core.containerPort" . }} + failureThreshold: 2 + periodSeconds: 10 + envFrom: + - configMapRef: + name: "{{ template "harbor.core" . }}" + - secretRef: + name: "{{ template "harbor.core" . }}" + env: + - name: CORE_SECRET + valueFrom: + secretKeyRef: + name: {{ template "harbor.core" . }} + key: secret + - name: JOBSERVICE_SECRET + valueFrom: + secretKeyRef: + name: "{{ template "harbor.jobservice" . }}" + key: JOBSERVICE_SECRET + {{- if .Values.internalTLS.enabled }} + - name: INTERNAL_TLS_ENABLED + value: "true" + - name: INTERNAL_TLS_KEY_PATH + value: /etc/harbor/ssl/core/tls.key + - name: INTERNAL_TLS_CERT_PATH + value: /etc/harbor/ssl/core/tls.crt + - name: INTERNAL_TLS_TRUST_CA_PATH + value: /etc/harbor/ssl/core/ca.crt + {{- end }} + ports: + - containerPort: {{ template "harbor.core.containerPort" . }} + volumeMounts: + - name: config + mountPath: /etc/core/app.conf + subPath: app.conf + - name: secret-key + mountPath: /etc/core/key + subPath: key + - name: token-service-private-key + mountPath: /etc/core/private_key.pem + subPath: tls.key + {{- if .Values.expose.tls.enabled }} + - name: ca-download + mountPath: /etc/core/ca + {{- end }} + {{- if .Values.uaaSecretName }} + - name: auth-ca-cert + mountPath: /etc/core/auth-ca/auth-ca.crt + subPath: auth-ca.crt + {{- end }} + {{- if .Values.internalTLS.enabled }} + - name: core-internal-certs + mountPath: /etc/harbor/ssl/core + {{- end }} + - name: psc + mountPath: /etc/core/token + {{- if .Values.caBundleSecretName }} +{{ include "harbor.caBundleVolumeMount" . | indent 8 }} + {{- end }} +{{- if .Values.core.resources }} + resources: +{{ toYaml .Values.core.resources | indent 10 }} +{{- end }} + volumes: + - name: config + configMap: + name: {{ template "harbor.core" . }} + items: + - key: app.conf + path: app.conf + - name: secret-key + secret: + secretName: {{ template "harbor.core" . }} + items: + - key: secretKey + path: key + - name: token-service-private-key + secret: + {{- if .Values.core.secretName }} + secretName: {{ .Values.core.secretName }} + {{- else }} + secretName: {{ template "harbor.core" . }} + {{- end }} + {{- if .Values.expose.tls.enabled }} + - name: ca-download + secret: + {{- if .Values.caSecretName }} + secretName: {{ .Values.caSecretName }} + {{- else if eq (include "harbor.autoGenCertForIngress" .) "true" }} + secretName: "{{ template "harbor.ingress" . }}" + {{- else if eq (include "harbor.autoGenCertForNginx" .) "true" }} + secretName: {{ template "harbor.tlsSecretForNginx" . }} + {{- end }} + {{- end }} + {{- if .Values.uaaSecretName }} + - name: auth-ca-cert + secret: + secretName: {{ .Values.uaaSecretName }} + items: + - key: ca.crt + path: auth-ca.crt + {{- end }} + {{- if .Values.internalTLS.enabled }} + - name: core-internal-certs + secret: + secretName: {{ template "harbor.internalTLS.core.secretName" . }} + {{- end }} + - name: psc + emptyDir: {} + {{- if .Values.caBundleSecretName }} +{{ include "harbor.caBundleVolume" . | indent 6 }} + {{- end }} + {{- with .Values.core.nodeSelector }} + nodeSelector: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.core.affinity }} + affinity: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.core.tolerations }} + tolerations: +{{ toYaml . | indent 8 }} + {{- end }} + {{- if .Values.core.priorityClassName }} + priorityClassName: {{ .Values.core.priorityClassName }} + {{- end }} diff --git a/ResearchOps/harbor/templates/core/core-pre-upgrade-job.yaml b/ResearchOps/harbor/templates/core/core-pre-upgrade-job.yaml new file mode 100644 index 0000000..9681965 --- /dev/null +++ b/ResearchOps/harbor/templates/core/core-pre-upgrade-job.yaml @@ -0,0 +1,66 @@ +{{- if .Values.enableMigrateHelmHook }} +apiVersion: batch/v1 +kind: Job +metadata: + name: migration-job + labels: +{{ include "harbor.labels" . | indent 4 }} + component: migrator + annotations: + # This is what defines this resource as a hook. Without this line, the + # job is considered part of the release. + "helm.sh/hook": pre-upgrade + "helm.sh/hook-weight": "-5" +spec: + template: + metadata: + labels: +{{ include "harbor.matchLabels" . | indent 8 }} + component: migrator + spec: + restartPolicy: Never + securityContext: + runAsUser: 10000 + fsGroup: 10000 +{{- if .Values.core.serviceAccountName }} + serviceAccountName: {{ .Values.core.serviceAccountName }} +{{- end -}} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + terminationGracePeriodSeconds: 120 + containers: + - name: core-job + image: {{ .Values.core.image.repository }}:{{ .Values.core.image.tag }} + imagePullPolicy: {{ .Values.imagePullPolicy }} + command: ["/harbor/harbor_core", "-mode=migrate"] + envFrom: + - configMapRef: + name: "{{ template "harbor.core" . }}" + - secretRef: + name: "{{ template "harbor.core" . }}" + volumeMounts: + - name: config + mountPath: /etc/core/app.conf + subPath: app.conf + volumes: + - name: config + configMap: + name: {{ template "harbor.core" . }} + items: + - key: app.conf + path: app.conf + {{- with .Values.core.nodeSelector }} + nodeSelector: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.core.affinity }} + affinity: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.core.tolerations }} + tolerations: +{{ toYaml . | indent 8 }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/ResearchOps/harbor/templates/core/core-secret.yaml b/ResearchOps/harbor/templates/core/core-secret.yaml new file mode 100644 index 0000000..6e3e446 --- /dev/null +++ b/ResearchOps/harbor/templates/core/core-secret.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "harbor.core" . }} + labels: +{{ include "harbor.labels" . | indent 4 }} +type: Opaque +data: + secretKey: {{ .Values.secretKey | b64enc | quote }} + secret: {{ .Values.core.secret | default (randAlphaNum 16) | b64enc | quote }} +{{- if not .Values.core.secretName }} + tls.crt: {{ .Files.Get "cert/tls.crt" | b64enc }} + tls.key: {{ .Files.Get "cert/tls.key" | b64enc }} +{{- end }} + HARBOR_ADMIN_PASSWORD: {{ .Values.harborAdminPassword | b64enc | quote }} + POSTGRESQL_PASSWORD: {{ template "harbor.database.encryptedPassword" . }} + REGISTRY_CREDENTIAL_PASSWORD: {{ .Values.registry.credentials.password | b64enc | quote }} + CSRF_KEY: {{ .Values.core.xsrfKey | default (randAlphaNum 32) | b64enc | quote }} + {{- template "harbor.traceJaegerPassword" . }} diff --git a/ResearchOps/harbor/templates/core/core-svc.yaml b/ResearchOps/harbor/templates/core/core-svc.yaml new file mode 100644 index 0000000..c918e6d --- /dev/null +++ b/ResearchOps/harbor/templates/core/core-svc.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ template "harbor.core" . }} + labels: +{{ include "harbor.labels" . | indent 4 }} +spec: +{{- if (eq .Values.expose.ingress.controller "gce") }} + type: NodePort +{{- end }} + ports: + - name: {{ ternary "https-web" "http-web" .Values.internalTLS.enabled }} + port: {{ template "harbor.core.servicePort" . }} + targetPort: {{ template "harbor.core.containerPort" . }} +{{- if .Values.metrics.enabled}} + - name: {{ template "harbor.metricsPortName" . }} + port: {{ .Values.metrics.core.port }} +{{- end }} + selector: +{{ include "harbor.matchLabels" . | indent 4 }} + component: core diff --git a/ResearchOps/harbor/templates/core/core-tls.yaml b/ResearchOps/harbor/templates/core/core-tls.yaml new file mode 100644 index 0000000..c52148f --- /dev/null +++ b/ResearchOps/harbor/templates/core/core-tls.yaml @@ -0,0 +1,15 @@ +{{- if and .Values.internalTLS.enabled }} +{{- if eq .Values.internalTLS.certSource "manual" }} +apiVersion: v1 +kind: Secret +metadata: + name: "{{ template "harbor.internalTLS.core.secretName" . }}" + labels: +{{ include "harbor.labels" . | indent 4 }} +type: kubernetes.io/tls +data: + ca.crt: {{ (required "The \"internalTLS.trustCa\" is required!" .Values.internalTLS.trustCa) | b64enc | quote }} + tls.crt: {{ (required "The \"internalTLS.core.crt\" is required!" .Values.internalTLS.core.crt) | b64enc | quote }} + tls.key: {{ (required "The \"internalTLS.core.key\" is required!" .Values.internalTLS.core.key) | b64enc | quote }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/ResearchOps/harbor/templates/database/database-secret.yaml b/ResearchOps/harbor/templates/database/database-secret.yaml new file mode 100644 index 0000000..864aff4 --- /dev/null +++ b/ResearchOps/harbor/templates/database/database-secret.yaml @@ -0,0 +1,11 @@ +{{- if eq .Values.database.type "internal" -}} +apiVersion: v1 +kind: Secret +metadata: + name: "{{ template "harbor.database" . }}" + labels: +{{ include "harbor.labels" . | indent 4 }} +type: Opaque +data: + POSTGRES_PASSWORD: {{ template "harbor.database.encryptedPassword" . }} +{{- end -}} diff --git a/ResearchOps/harbor/templates/database/database-ss.yaml b/ResearchOps/harbor/templates/database/database-ss.yaml new file mode 100644 index 0000000..c476c35 --- /dev/null +++ b/ResearchOps/harbor/templates/database/database-ss.yaml @@ -0,0 +1,160 @@ +{{- if eq .Values.database.type "internal" -}} +{{- $database := .Values.persistence.persistentVolumeClaim.database -}} +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: "{{ template "harbor.database" . }}" + labels: +{{ include "harbor.labels" . | indent 4 }} + component: database +spec: + replicas: 1 + serviceName: "{{ template "harbor.database" . }}" + selector: + matchLabels: +{{ include "harbor.matchLabels" . | indent 6 }} + component: database + template: + metadata: + labels: +{{ include "harbor.labels" . | indent 8 }} + component: database + annotations: + checksum/secret: {{ include (print $.Template.BasePath "/database/database-secret.yaml") . | sha256sum }} +{{- if .Values.database.podAnnotations }} +{{ toYaml .Values.database.podAnnotations | indent 8 }} +{{- end }} + spec: + securityContext: + runAsUser: 999 + fsGroup: 999 +{{- if .Values.database.internal.serviceAccountName }} + serviceAccountName: {{ .Values.database.internal.serviceAccountName }} +{{- end -}} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + automountServiceAccountToken: {{ .Values.database.internal.automountServiceAccountToken | default false }} + terminationGracePeriodSeconds: 120 + initContainers: + # as we change the data directory to a sub folder to support psp, the init container here + # is used to migrate the existing data. See https://github.com/goharbor/harbor-helm/issues/756 + # for more detail. + # we may remove it after several releases + - name: "data-migrator" + image: {{ .Values.database.internal.image.repository }}:{{ .Values.database.internal.image.tag }} + imagePullPolicy: {{ .Values.imagePullPolicy }} + command: ["/bin/sh"] + args: ["-c", "[ -e /var/lib/postgresql/data/postgresql.conf ] && [ ! -d /var/lib/postgresql/data/pgdata ] && mkdir -m 0700 /var/lib/postgresql/data/pgdata && mv /var/lib/postgresql/data/* /var/lib/postgresql/data/pgdata/ || true"] +{{- if .Values.database.internal.initContainer.migrator.resources }} + resources: +{{ toYaml .Values.database.internal.initContainer.migrator.resources | indent 10 }} +{{- end }} + volumeMounts: + - name: database-data + mountPath: /var/lib/postgresql/data + subPath: {{ $database.subPath }} + # with "fsGroup" set, each time a volume is mounted, Kubernetes must recursively chown() and chmod() all the files and directories inside the volume + # this causes the postgresql reports the "data directory /var/lib/postgresql/data/pgdata has group or world access" issue when using some CSIs e.g. Ceph + # use this init container to correct the permission + # as "fsGroup" applied before the init container running, the container has enough permission to execute the command + - name: "data-permissions-ensurer" + image: {{ .Values.database.internal.image.repository }}:{{ .Values.database.internal.image.tag }} + imagePullPolicy: {{ .Values.imagePullPolicy }} + command: ["/bin/sh"] + args: ["-c", "chmod -R 700 /var/lib/postgresql/data/pgdata || true"] +{{- if .Values.database.internal.initContainer.permissions.resources }} + resources: +{{ toYaml .Values.database.internal.initContainer.permissions.resources | indent 10 }} +{{- end }} + volumeMounts: + - name: database-data + mountPath: /var/lib/postgresql/data + subPath: {{ $database.subPath }} + containers: + - name: database + image: {{ .Values.database.internal.image.repository }}:{{ .Values.database.internal.image.tag }} + imagePullPolicy: {{ .Values.imagePullPolicy }} + livenessProbe: + exec: + command: + - /docker-healthcheck.sh + initialDelaySeconds: 300 + periodSeconds: 10 + readinessProbe: + exec: + command: + - /docker-healthcheck.sh + initialDelaySeconds: 1 + periodSeconds: 10 +{{- if .Values.database.internal.resources }} + resources: +{{ toYaml .Values.database.internal.resources | indent 10 }} +{{- end }} + envFrom: + - secretRef: + name: "{{ template "harbor.database" . }}" + env: + # put the data into a sub directory to avoid the permission issue in k8s with restricted psp enabled + # more detail refer to https://github.com/goharbor/harbor-helm/issues/756 + - name: PGDATA + value: "/var/lib/postgresql/data/pgdata" + volumeMounts: + - name: database-data + mountPath: /var/lib/postgresql/data + subPath: {{ $database.subPath }} + - name: shm-volume + mountPath: /dev/shm + volumes: + - name: shm-volume + emptyDir: + medium: Memory + sizeLimit: {{ .Values.database.internal.shmSizeLimit }} + {{- if not .Values.persistence.enabled }} + - name: "database-data" + emptyDir: {} + {{- else if $database.existingClaim }} + - name: "database-data" + persistentVolumeClaim: + claimName: {{ $database.existingClaim }} + {{- end -}} + {{- with .Values.database.internal.nodeSelector }} + nodeSelector: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.database.internal.affinity }} + affinity: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.database.internal.tolerations }} + tolerations: +{{ toYaml . | indent 8 }} + {{- end }} + {{- if .Values.database.internal.priorityClassName }} + priorityClassName: {{ .Values.database.internal.priorityClassName }} + {{- end }} + {{- if and .Values.persistence.enabled (not $database.existingClaim) }} + volumeClaimTemplates: + - metadata: + name: "database-data" + labels: +{{ include "harbor.labels" . | indent 8 }} + annotations: + {{- range $key, $value := $database.annotations }} + {{ $key }}: {{ $value | quote }} + {{- end }} + spec: + accessModes: [{{ $database.accessMode | quote }}] + {{- if $database.storageClass }} + {{- if (eq "-" $database.storageClass) }} + storageClassName: "" + {{- else }} + storageClassName: "{{ $database.storageClass }}" + {{- end }} + {{- end }} + resources: + requests: + storage: {{ $database.size | quote }} + {{- end -}} + {{- end -}} diff --git a/ResearchOps/harbor/templates/database/database-svc.yaml b/ResearchOps/harbor/templates/database/database-svc.yaml new file mode 100644 index 0000000..6475048 --- /dev/null +++ b/ResearchOps/harbor/templates/database/database-svc.yaml @@ -0,0 +1,14 @@ +{{- if eq .Values.database.type "internal" -}} +apiVersion: v1 +kind: Service +metadata: + name: "{{ template "harbor.database" . }}" + labels: +{{ include "harbor.labels" . | indent 4 }} +spec: + ports: + - port: 5432 + selector: +{{ include "harbor.matchLabels" . | indent 4 }} + component: database +{{- end -}} \ No newline at end of file diff --git a/ResearchOps/harbor/templates/exporter/exporter-cm-env.yaml b/ResearchOps/harbor/templates/exporter/exporter-cm-env.yaml new file mode 100644 index 0000000..0bf4e7d --- /dev/null +++ b/ResearchOps/harbor/templates/exporter/exporter-cm-env.yaml @@ -0,0 +1,35 @@ +{{- if .Values.metrics.enabled}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: "{{ template "harbor.exporter" . }}-env" + labels: +{{ include "harbor.labels" . | indent 4 }} +data: + {{- if has "jobservice" .Values.proxy.components }} + HTTP_PROXY: "{{ .Values.proxy.httpProxy }}" + HTTPS_PROXY: "{{ .Values.proxy.httpsProxy }}" + NO_PROXY: "{{ template "harbor.noProxy" . }}" + {{- end }} + LOG_LEVEL: "{{ .Values.logLevel }}" + HARBOR_EXPORTER_PORT: "{{ .Values.metrics.exporter.port }}" + HARBOR_EXPORTER_METRICS_PATH: "{{ .Values.metrics.exporter.path }}" + HARBOR_EXPORTER_METRICS_ENABLED: "{{ .Values.metrics.enabled }}" + HARBOR_EXPORTER_CACHE_TIME: "{{ .Values.exporter.cacheDuration }}" + HARBOR_EXPORTER_CACHE_CLEAN_INTERVAL: "{{ .Values.exporter.cacheCleanInterval }}" + HARBOR_METRIC_NAMESPACE: harbor + HARBOR_METRIC_SUBSYSTEM: exporter + HARBOR_REDIS_URL: "{{ template "harbor.redis.urlForJobservice" . }}" + HARBOR_REDIS_NAMESPACE: harbor_job_service_namespace + HARBOR_REDIS_TIMEOUT: "3600" + HARBOR_SERVICE_SCHEME: "{{ template "harbor.component.scheme" . }}" + HARBOR_SERVICE_HOST: "{{ template "harbor.core" . }}" + HARBOR_SERVICE_PORT: "{{ template "harbor.core.servicePort" . }}" + HARBOR_DATABASE_HOST: "{{ template "harbor.database.host" . }}" + HARBOR_DATABASE_PORT: "{{ template "harbor.database.port" . }}" + HARBOR_DATABASE_USERNAME: "{{ template "harbor.database.username" . }}" + HARBOR_DATABASE_DBNAME: "{{ template "harbor.database.coreDatabase" . }}" + HARBOR_DATABASE_SSLMODE: "{{ template "harbor.database.sslmode" . }}" + HARBOR_DATABASE_MAX_IDLE_CONNS: "{{ .Values.database.maxIdleConns }}" + HARBOR_DATABASE_MAX_OPEN_CONNS: "{{ .Values.database.maxOpenConns }}" +{{- end}} \ No newline at end of file diff --git a/ResearchOps/harbor/templates/exporter/exporter-dpl.yaml b/ResearchOps/harbor/templates/exporter/exporter-dpl.yaml new file mode 100644 index 0000000..8f13b8a --- /dev/null +++ b/ResearchOps/harbor/templates/exporter/exporter-dpl.yaml @@ -0,0 +1,102 @@ +{{- if .Values.metrics.enabled}} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "harbor.exporter" . }} + labels: +{{ include "harbor.labels" . | indent 4 }} + component: exporter +spec: + replicas: {{ .Values.exporter.replicas }} + revisionHistoryLimit: {{ .Values.exporter.revisionHistoryLimit }} + selector: + matchLabels: +{{ include "harbor.matchLabels" . | indent 6 }} + component: exporter + template: + metadata: + labels: +{{ include "harbor.labels" . | indent 8 }} + component: exporter + annotations: +{{- if .Values.exporter.podAnnotations }} +{{ toYaml .Values.exporter.podAnnotations | indent 8 }} +{{- end }} + spec: + securityContext: + runAsUser: 10000 + fsGroup: 10000 +{{- if .Values.exporter.serviceAccountName }} + serviceAccountName: {{ .Values.exporter.serviceAccountName }} +{{- end -}} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + automountServiceAccountToken: {{ .Values.exporter.automountServiceAccountToken | default false }} + containers: + - name: exporter + image: {{ .Values.exporter.image.repository }}:{{ .Values.exporter.image.tag }} + imagePullPolicy: {{ .Values.imagePullPolicy }} + livenessProbe: + httpGet: + path: / + port: {{ .Values.metrics.exporter.port }} + initialDelaySeconds: 300 + periodSeconds: 10 + readinessProbe: + httpGet: + path: / + port: {{ .Values.metrics.exporter.port }} + initialDelaySeconds: 30 + periodSeconds: 10 + args: ["-log-level", "{{ .Values.logLevel }}"] + envFrom: + - configMapRef: + name: "{{ template "harbor.exporter" . }}-env" + - secretRef: + name: "{{ template "harbor.exporter" . }}" +{{- if .Values.exporter.resources }} + resources: +{{ toYaml .Values.exporter.resources | indent 10 }} +{{- end }} + ports: + - containerPort: {{ template "harbor.core.containerPort" . }} + volumeMounts: + {{- if .Values.caBundleSecretName }} +{{ include "harbor.caBundleVolumeMount" . | indent 8 }} + {{- end }} + {{- if .Values.internalTLS.enabled }} + - name: core-internal-certs + mountPath: /etc/harbor/ssl/core + # There are some metric data are collectd from harbor core. + # When internal TLS is enabled, the Exporter need the CA file to collect these data. + {{- end }} + volumes: + - name: config + secret: + secretName: "{{ template "harbor.exporter" . }}" + {{- if .Values.internalTLS.enabled }} + - name: core-internal-certs + secret: + secretName: {{ template "harbor.internalTLS.core.secretName" . }} + {{- end }} + {{- if .Values.caBundleSecretName }} +{{ include "harbor.caBundleVolume" . | indent 6 }} + {{- end }} + {{- with .Values.exporter.nodeSelector }} + nodeSelector: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.exporter.affinity }} + affinity: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.exporter.tolerations }} + tolerations: +{{ toYaml . | indent 8 }} + {{- end }} + {{- if .Values.exporter.priorityClassName }} + priorityClassName: {{ .Values.exporter.priorityClassName }} + {{- end }} +{{ end }} diff --git a/ResearchOps/harbor/templates/exporter/exporter-secret.yaml b/ResearchOps/harbor/templates/exporter/exporter-secret.yaml new file mode 100644 index 0000000..1fa6b49 --- /dev/null +++ b/ResearchOps/harbor/templates/exporter/exporter-secret.yaml @@ -0,0 +1,16 @@ +{{- if .Values.metrics.enabled}} +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "harbor.exporter" . }} + labels: +{{ include "harbor.labels" . | indent 4 }} +type: Opaque +data: +{{- if not .Values.exporter.secretName }} + tls.crt: {{ .Files.Get "cert/tls.crt" | b64enc }} + tls.key: {{ .Files.Get "cert/tls.key" | b64enc }} +{{- end }} + HARBOR_ADMIN_PASSWORD: {{ .Values.harborAdminPassword | b64enc | quote }} + HARBOR_DATABASE_PASSWORD: {{ template "harbor.database.encryptedPassword" . }} +{{- end }} \ No newline at end of file diff --git a/ResearchOps/harbor/templates/exporter/exporter-svc.yaml b/ResearchOps/harbor/templates/exporter/exporter-svc.yaml new file mode 100644 index 0000000..4a6f3fd --- /dev/null +++ b/ResearchOps/harbor/templates/exporter/exporter-svc.yaml @@ -0,0 +1,15 @@ +{{- if .Values.metrics.enabled}} +apiVersion: v1 +kind: Service +metadata: + name: "{{ template "harbor.exporter" . }}" + labels: +{{ include "harbor.labels" . | indent 4 }} +spec: + ports: + - name: {{ template "harbor.metricsPortName" . }} + port: {{ .Values.metrics.exporter.port }} + selector: +{{ include "harbor.matchLabels" . | indent 4 }} + component: exporter +{{ end }} diff --git a/ResearchOps/harbor/templates/ingress/ingress.yaml b/ResearchOps/harbor/templates/ingress/ingress.yaml new file mode 100644 index 0000000..64dbadc --- /dev/null +++ b/ResearchOps/harbor/templates/ingress/ingress.yaml @@ -0,0 +1,209 @@ +{{- if eq .Values.expose.type "ingress" }} +{{- $ingress := .Values.expose.ingress -}} +{{- $tls := .Values.expose.tls -}} +{{- if eq .Values.expose.ingress.controller "gce" }} + {{- $_ := set . "portal_path" "/*" -}} + {{- $_ := set . "api_path" "/api/*" -}} + {{- $_ := set . "service_path" "/service/*" -}} + {{- $_ := set . "v2_path" "/v2/*" -}} + {{- $_ := set . "chartrepo_path" "/chartrepo/*" -}} + {{- $_ := set . "controller_path" "/c/*" -}} + {{- $_ := set . "notary_path" "/" -}} +{{- else if eq .Values.expose.ingress.controller "ncp" }} + {{- $_ := set . "portal_path" "/.*" -}} + {{- $_ := set . "api_path" "/api/.*" -}} + {{- $_ := set . "service_path" "/service/.*" -}} + {{- $_ := set . "v2_path" "/v2/.*" -}} + {{- $_ := set . "chartrepo_path" "/chartrepo/.*" -}} + {{- $_ := set . "controller_path" "/c/.*" -}} + {{- $_ := set . "notary_path" "/.*" -}} +{{- else }} + {{- $_ := set . "portal_path" "/" -}} + {{- $_ := set . "api_path" "/api/" -}} + {{- $_ := set . "service_path" "/service/" -}} + {{- $_ := set . "v2_path" "/v2" -}} + {{- $_ := set . "chartrepo_path" "/chartrepo/" -}} + {{- $_ := set . "controller_path" "/c/" -}} + {{- $_ := set . "notary_path" "/" -}} +{{- end }} + +--- +{{- if semverCompare "<1.14-0" (include "harbor.ingress.kubeVersion" .) }} +apiVersion: extensions/v1beta1 +{{- else if semverCompare "<1.19-0" (include "harbor.ingress.kubeVersion" .) }} +apiVersion: networking.k8s.io/v1beta1 +{{- else }} +apiVersion: networking.k8s.io/v1 +{{- end }} +kind: Ingress +metadata: + name: "{{ template "harbor.ingress" . }}" + labels: +{{ include "harbor.labels" . | indent 4 }} +{{- if $ingress.harbor.labels }} +{{ toYaml $ingress.harbor.labels | indent 4 }} +{{- end }} + annotations: +{{ toYaml $ingress.annotations | indent 4 }} +{{- if .Values.internalTLS.enabled }} + nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" +{{- end }} +{{- if eq .Values.expose.ingress.controller "ncp" }} + ncp/use-regex: "true" + {{- if $tls.enabled }} + ncp/http-redirect: "true" + {{- end }} +{{- end }} +{{- if $ingress.harbor.annotations }} +{{ toYaml $ingress.harbor.annotations | indent 4 }} +{{- end }} +spec: + {{- if $ingress.className }} + ingressClassName: {{ $ingress.className }} + {{- end }} + {{- if $tls.enabled }} + tls: + - secretName: {{ template "harbor.tlsCoreSecretForIngress" . }} + {{- if $ingress.hosts.core }} + hosts: + - {{ $ingress.hosts.core }} + {{- end }} + {{- end }} + rules: + - http: + paths: +{{- if semverCompare "<1.19-0" (include "harbor.ingress.kubeVersion" .) }} + - path: {{ .api_path }} + backend: + serviceName: {{ template "harbor.core" . }} + servicePort: {{ template "harbor.core.servicePort" . }} + - path: {{ .service_path }} + backend: + serviceName: {{ template "harbor.core" . }} + servicePort: {{ template "harbor.core.servicePort" . }} + - path: {{ .v2_path }} + backend: + serviceName: {{ template "harbor.core" . }} + servicePort: {{ template "harbor.core.servicePort" . }} + - path: {{ .chartrepo_path }} + backend: + serviceName: {{ template "harbor.core" . }} + servicePort: {{ template "harbor.core.servicePort" . }} + - path: {{ .controller_path }} + backend: + serviceName: {{ template "harbor.core" . }} + servicePort: {{ template "harbor.core.servicePort" . }} + - path: {{ .portal_path }} + backend: + serviceName: {{ template "harbor.portal" . }} + servicePort: {{ template "harbor.portal.servicePort" . }} +{{- else }} + - path: {{ .api_path }} + pathType: Prefix + backend: + service: + name: {{ template "harbor.core" . }} + port: + number: {{ template "harbor.core.servicePort" . }} + - path: {{ .service_path }} + pathType: Prefix + backend: + service: + name: {{ template "harbor.core" . }} + port: + number: {{ template "harbor.core.servicePort" . }} + - path: {{ .v2_path }} + pathType: Prefix + backend: + service: + name: {{ template "harbor.core" . }} + port: + number: {{ template "harbor.core.servicePort" . }} + - path: {{ .chartrepo_path }} + pathType: Prefix + backend: + service: + name: {{ template "harbor.core" . }} + port: + number: {{ template "harbor.core.servicePort" . }} + - path: {{ .controller_path }} + pathType: Prefix + backend: + service: + name: {{ template "harbor.core" . }} + port: + number: {{ template "harbor.core.servicePort" . }} + - path: {{ .portal_path }} + pathType: Prefix + backend: + service: + name: {{ template "harbor.portal" . }} + port: + number: {{ template "harbor.portal.servicePort" . }} +{{- end }} + {{- if $ingress.hosts.core }} + host: {{ $ingress.hosts.core }} + {{- end }} + +{{- if .Values.notary.enabled }} +--- +{{- if semverCompare "<1.14-0" (include "harbor.ingress.kubeVersion" .) }} +apiVersion: extensions/v1beta1 +{{- else if semverCompare "<1.19-0" (include "harbor.ingress.kubeVersion" .) }} +apiVersion: networking.k8s.io/v1beta1 +{{- else }} +apiVersion: networking.k8s.io/v1 +{{- end }} +kind: Ingress +metadata: + name: "{{ template "harbor.ingress-notary" . }}" + labels: +{{ include "harbor.labels" . | indent 4 }} +{{- if $ingress.notary.labels }} +{{ toYaml $ingress.notary.labels | indent 4 }} +{{- end }} + annotations: +{{ toYaml $ingress.annotations | indent 4 }} +{{- if eq .Values.expose.ingress.controller "ncp" }} + ncp/use-regex: "true" + {{- if $tls.enabled }} + ncp/http-redirect: "true" + {{- end }} +{{- end }} +{{- if $ingress.notary.annotations }} +{{ toYaml $ingress.notary.annotations | indent 4 }} +{{- end }} +spec: + {{- if $ingress.className }} + ingressClassName: {{ $ingress.className }} + {{- end }} + {{- if $tls.enabled }} + tls: + - secretName: {{ template "harbor.tlsNotarySecretForIngress" . }} + {{- if $ingress.hosts.notary }} + hosts: + - {{ $ingress.hosts.notary }} + {{- end }} + {{- end }} + rules: + - http: + paths: + - path: {{ .notary_path }} +{{- if semverCompare "<1.19-0" (include "harbor.ingress.kubeVersion" .) }} + backend: + serviceName: {{ template "harbor.notary-server" . }} + servicePort: 4443 +{{- else }} + pathType: Prefix + backend: + service: + name: {{ template "harbor.notary-server" . }} + port: + number: 4443 +{{- end -}} + {{- if $ingress.hosts.notary }} + host: {{ $ingress.hosts.notary }} + {{- end }} +{{- end }} + +{{- end }} diff --git a/ResearchOps/harbor/templates/ingress/secret.yaml b/ResearchOps/harbor/templates/ingress/secret.yaml new file mode 100644 index 0000000..0d89af9 --- /dev/null +++ b/ResearchOps/harbor/templates/ingress/secret.yaml @@ -0,0 +1,15 @@ +{{- if eq (include "harbor.autoGenCertForIngress" .) "true" }} +{{- $ca := genCA "harbor-ca" 365 }} +{{- $cert := genSignedCert .Values.expose.ingress.hosts.core nil (list .Values.expose.ingress.hosts.core .Values.expose.ingress.hosts.notary) 365 $ca }} +apiVersion: v1 +kind: Secret +metadata: + name: "{{ template "harbor.ingress" . }}" + labels: +{{ include "harbor.labels" . | indent 4 }} +type: kubernetes.io/tls +data: + tls.crt: {{ $cert.Cert | b64enc | quote }} + tls.key: {{ $cert.Key | b64enc | quote }} + ca.crt: {{ $ca.Cert | b64enc | quote }} +{{- end }} \ No newline at end of file diff --git a/ResearchOps/harbor/templates/internal/auto-tls.yaml b/ResearchOps/harbor/templates/internal/auto-tls.yaml new file mode 100644 index 0000000..6c7a5c9 --- /dev/null +++ b/ResearchOps/harbor/templates/internal/auto-tls.yaml @@ -0,0 +1,98 @@ +{{- if and .Values.internalTLS.enabled (eq .Values.internalTLS.certSource "auto") }} +{{- $ca := genCA "harbor-internal-ca" 365 }} +{{- $coreCN := (include "harbor.core" .) }} +{{- $coreCrt := genSignedCert $coreCN (list "127.0.0.1") (list "localhost" $coreCN) 365 $ca }} +{{- $jsCN := (include "harbor.jobservice" .) }} +{{- $jsCrt := genSignedCert $jsCN nil (list $jsCN) 365 $ca }} +{{- $regCN := (include "harbor.registry" .) }} +{{- $regCrt := genSignedCert $regCN nil (list $regCN) 365 $ca }} +{{- $portalCN := (include "harbor.portal" .) }} +{{- $portalCrt := genSignedCert $portalCN nil (list $portalCN) 365 $ca }} + +--- +apiVersion: v1 +kind: Secret +metadata: + name: "{{ template "harbor.internalTLS.core.secretName" . }}" + labels: +{{ include "harbor.labels" . | indent 4 }} +type: kubernetes.io/tls +data: + ca.crt: {{ $ca.Cert | b64enc | quote }} + tls.crt: {{ $coreCrt.Cert | b64enc | quote }} + tls.key: {{ $coreCrt.Key | b64enc | quote }} + +--- +apiVersion: v1 +kind: Secret +metadata: + name: "{{ template "harbor.internalTLS.jobservice.secretName" . }}" + labels: +{{ include "harbor.labels" . | indent 4 }} +type: kubernetes.io/tls +data: + ca.crt: {{ $ca.Cert | b64enc | quote }} + tls.crt: {{ $jsCrt.Cert | b64enc | quote }} + tls.key: {{ $jsCrt.Key | b64enc | quote }} + +--- +apiVersion: v1 +kind: Secret +metadata: + name: "{{ template "harbor.internalTLS.registry.secretName" . }}" + labels: +{{ include "harbor.labels" . | indent 4 }} +type: kubernetes.io/tls +data: + ca.crt: {{ $ca.Cert | b64enc | quote }} + tls.crt: {{ $regCrt.Cert | b64enc | quote }} + tls.key: {{ $regCrt.Key | b64enc | quote }} + +--- +apiVersion: v1 +kind: Secret +metadata: + name: "{{ template "harbor.internalTLS.portal.secretName" . }}" + labels: +{{ include "harbor.labels" . | indent 4 }} +type: kubernetes.io/tls +data: + ca.crt: {{ $ca.Cert | b64enc | quote }} + tls.crt: {{ $portalCrt.Cert | b64enc | quote }} + tls.key: {{ $portalCrt.Key | b64enc | quote }} + +{{- if .Values.chartmuseum.enabled }} +--- +{{- $chartCN := (include "harbor.chartmuseum" .) }} +{{- $chartCrt := genSignedCert $chartCN nil (list $chartCN) 365 $ca }} +apiVersion: v1 +kind: Secret +metadata: + name: "{{ template "harbor.internalTLS.chartmuseum.secretName" . }}" + labels: +{{ include "harbor.labels" . | indent 4 }} +type: kubernetes.io/tls +data: + ca.crt: {{ $ca.Cert | b64enc | quote }} + tls.crt: {{ $chartCrt.Cert | b64enc | quote }} + tls.key: {{ $chartCrt.Key | b64enc | quote }} +{{- end }} + +{{- if and .Values.trivy.enabled}} +--- +{{- $trivyCN := (include "harbor.trivy" .) }} +{{- $trivyCrt := genSignedCert $trivyCN nil (list $trivyCN) 365 $ca }} +apiVersion: v1 +kind: Secret +metadata: + name: "{{ template "harbor.internalTLS.trivy.secretName" . }}" + labels: +{{ include "harbor.labels" . | indent 4 }} +type: kubernetes.io/tls +data: + ca.crt: {{ $ca.Cert | b64enc | quote }} + tls.crt: {{ $trivyCrt.Cert | b64enc | quote }} + tls.key: {{ $trivyCrt.Key | b64enc | quote }} +{{- end }} + +{{- end }} \ No newline at end of file diff --git a/ResearchOps/harbor/templates/jobservice/jobservice-cm-env.yaml b/ResearchOps/harbor/templates/jobservice/jobservice-cm-env.yaml new file mode 100644 index 0000000..656a171 --- /dev/null +++ b/ResearchOps/harbor/templates/jobservice/jobservice-cm-env.yaml @@ -0,0 +1,22 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: "{{ template "harbor.jobservice" . }}-env" + labels: +{{ include "harbor.labels" . | indent 4 }} +data: + CORE_URL: "{{ template "harbor.coreURL" . }}" + TOKEN_SERVICE_URL: "{{ template "harbor.tokenServiceURL" . }}" + REGISTRY_URL: "{{ template "harbor.registryURL" . }}" + REGISTRY_CONTROLLER_URL: "{{ template "harbor.registryControllerURL" . }}" + REGISTRY_CREDENTIAL_USERNAME: "{{ .Values.registry.credentials.username }}" + {{- if has "jobservice" .Values.proxy.components }} + HTTP_PROXY: "{{ .Values.proxy.httpProxy }}" + HTTPS_PROXY: "{{ .Values.proxy.httpsProxy }}" + NO_PROXY: "{{ template "harbor.noProxy" . }}" + {{- end }} + {{- if .Values.metrics.enabled}} + METRIC_NAMESPACE: harbor + METRIC_SUBSYSTEM: jobservice + {{- end }} + {{- template "harbor.traceEnvsForJobservice" . }} diff --git a/ResearchOps/harbor/templates/jobservice/jobservice-cm.yaml b/ResearchOps/harbor/templates/jobservice/jobservice-cm.yaml new file mode 100644 index 0000000..6500475 --- /dev/null +++ b/ResearchOps/harbor/templates/jobservice/jobservice-cm.yaml @@ -0,0 +1,52 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: "{{ template "harbor.jobservice" . }}" + labels: +{{ include "harbor.labels" . | indent 4 }} +data: + config.yml: |+ + #Server listening port + protocol: "{{ template "harbor.component.scheme" . }}" + port: {{ template "harbor.jobservice.containerPort". }} + {{- if .Values.internalTLS.enabled }} + https_config: + cert: "/etc/harbor/ssl/jobservice/tls.crt" + key: "/etc/harbor/ssl/jobservice/tls.key" + {{- end }} + worker_pool: + workers: {{ .Values.jobservice.maxJobWorkers }} + backend: "redis" + redis_pool: + redis_url: "{{ template "harbor.redis.urlForJobservice" . }}" + namespace: "harbor_job_service_namespace" + idle_timeout_second: 3600 + job_loggers: + {{- if has "file" .Values.jobservice.jobLoggers }} + - name: "FILE" + level: {{ .Values.logLevel | upper }} + settings: # Customized settings of logger + base_dir: "/var/log/jobs" + sweeper: + duration: {{ .Values.jobservice.loggerSweeperDuration }} #days + settings: # Customized settings of sweeper + work_dir: "/var/log/jobs" + {{- end }} + {{- if has "database" .Values.jobservice.jobLoggers }} + - name: "DB" + level: {{ .Values.logLevel | upper }} + sweeper: + duration: {{ .Values.jobservice.loggerSweeperDuration }} #days + {{- end }} + {{- if has "stdout" .Values.jobservice.jobLoggers }} + - name: "STD_OUTPUT" + level: {{ .Values.logLevel | upper }} + {{- end }} + metric: + enabled: {{ .Values.metrics.enabled }} + path: {{ .Values.metrics.jobservice.path }} + port: {{ .Values.metrics.jobservice.port }} + #Loggers for the job service + loggers: + - name: "STD_OUTPUT" + level: {{ .Values.logLevel | upper }} \ No newline at end of file diff --git a/ResearchOps/harbor/templates/jobservice/jobservice-dpl.yaml b/ResearchOps/harbor/templates/jobservice/jobservice-dpl.yaml new file mode 100644 index 0000000..b988027 --- /dev/null +++ b/ResearchOps/harbor/templates/jobservice/jobservice-dpl.yaml @@ -0,0 +1,143 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: "{{ template "harbor.jobservice" . }}" + labels: +{{ include "harbor.labels" . | indent 4 }} + component: jobservice +spec: + replicas: {{ .Values.jobservice.replicas }} + revisionHistoryLimit: {{ .Values.jobservice.revisionHistoryLimit }} + strategy: + type: {{ .Values.updateStrategy.type }} + {{- if eq .Values.updateStrategy.type "Recreate" }} + rollingUpdate: null + {{- end }} + selector: + matchLabels: +{{ include "harbor.matchLabels" . | indent 6 }} + component: jobservice + template: + metadata: + labels: +{{ include "harbor.labels" . | indent 8 }} + component: jobservice + annotations: + checksum/configmap: {{ include (print $.Template.BasePath "/jobservice/jobservice-cm.yaml") . | sha256sum }} + checksum/configmap-env: {{ include (print $.Template.BasePath "/jobservice/jobservice-cm-env.yaml") . | sha256sum }} + checksum/secret: {{ include (print $.Template.BasePath "/jobservice/jobservice-secrets.yaml") . | sha256sum }} + checksum/secret-core: {{ include (print $.Template.BasePath "/core/core-secret.yaml") . | sha256sum }} +{{- if and .Values.internalTLS.enabled (eq .Values.internalTLS.certSource "auto") }} + checksum/tls: {{ include (print $.Template.BasePath "/internal/auto-tls.yaml") . | sha256sum }} +{{- else if and .Values.internalTLS.enabled (eq .Values.internalTLS.certSource "manual") }} + checksum/tls: {{ include (print $.Template.BasePath "/jobservice/jobservice-tls.yaml") . | sha256sum }} +{{- end }} +{{- if .Values.jobservice.podAnnotations }} +{{ toYaml .Values.jobservice.podAnnotations | indent 8 }} +{{- end }} + spec: + securityContext: + runAsUser: 10000 + fsGroup: 10000 +{{- if .Values.jobservice.serviceAccountName }} + serviceAccountName: {{ .Values.jobservice.serviceAccountName }} +{{- end -}} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + automountServiceAccountToken: {{ .Values.jobservice.automountServiceAccountToken | default false }} + terminationGracePeriodSeconds: 120 + containers: + - name: jobservice + image: {{ .Values.jobservice.image.repository }}:{{ .Values.jobservice.image.tag }} + imagePullPolicy: {{ .Values.imagePullPolicy }} + livenessProbe: + httpGet: + path: /api/v1/stats + scheme: {{ include "harbor.component.scheme" . | upper }} + port: {{ template "harbor.jobservice.containerPort" . }} + initialDelaySeconds: 300 + periodSeconds: 10 + readinessProbe: + httpGet: + path: /api/v1/stats + scheme: {{ include "harbor.component.scheme" . | upper }} + port: {{ template "harbor.jobservice.containerPort" . }} + initialDelaySeconds: 20 + periodSeconds: 10 +{{- if .Values.jobservice.resources }} + resources: +{{ toYaml .Values.jobservice.resources | indent 10 }} +{{- end }} + env: + - name: CORE_SECRET + valueFrom: + secretKeyRef: + name: {{ template "harbor.core" . }} + key: secret + {{- if .Values.internalTLS.enabled }} + - name: INTERNAL_TLS_ENABLED + value: "true" + - name: INTERNAL_TLS_KEY_PATH + value: /etc/harbor/ssl/jobservice/tls.key + - name: INTERNAL_TLS_CERT_PATH + value: /etc/harbor/ssl/jobservice/tls.crt + - name: INTERNAL_TLS_TRUST_CA_PATH + value: /etc/harbor/ssl/jobservice/ca.crt + {{- end }} + envFrom: + - configMapRef: + name: "{{ template "harbor.jobservice" . }}-env" + - secretRef: + name: "{{ template "harbor.jobservice" . }}" + ports: + - containerPort: {{ template "harbor.jobservice.containerPort" . }} + volumeMounts: + - name: jobservice-config + mountPath: /etc/jobservice/config.yml + subPath: config.yml + - name: job-logs + mountPath: /var/log/jobs + subPath: {{ .Values.persistence.persistentVolumeClaim.jobservice.subPath }} + {{- if .Values.internalTLS.enabled }} + - name: jobservice-internal-certs + mountPath: /etc/harbor/ssl/jobservice + {{- end }} + {{- if .Values.caBundleSecretName }} +{{ include "harbor.caBundleVolumeMount" . | indent 8 }} + {{- end }} + volumes: + - name: jobservice-config + configMap: + name: "{{ template "harbor.jobservice" . }}" + - name: job-logs + {{- if and .Values.persistence.enabled (has "file" .Values.jobservice.jobLoggers) }} + persistentVolumeClaim: + claimName: {{ .Values.persistence.persistentVolumeClaim.jobservice.existingClaim | default (include "harbor.jobservice" .) }} + {{- else }} + emptyDir: {} + {{- end }} + {{- if .Values.internalTLS.enabled }} + - name: jobservice-internal-certs + secret: + secretName: {{ template "harbor.internalTLS.jobservice.secretName" . }} + {{- end }} + {{- if .Values.caBundleSecretName }} +{{ include "harbor.caBundleVolume" . | indent 6 }} + {{- end }} + {{- with .Values.jobservice.nodeSelector }} + nodeSelector: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.jobservice.affinity }} + affinity: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.jobservice.tolerations }} + tolerations: +{{ toYaml . | indent 8 }} + {{- end }} + {{- if .Values.jobservice.priorityClassName }} + priorityClassName: {{ .Values.jobservice.priorityClassName }} + {{- end }} diff --git a/ResearchOps/harbor/templates/jobservice/jobservice-pvc.yaml b/ResearchOps/harbor/templates/jobservice/jobservice-pvc.yaml new file mode 100644 index 0000000..1f6d32d --- /dev/null +++ b/ResearchOps/harbor/templates/jobservice/jobservice-pvc.yaml @@ -0,0 +1,30 @@ +{{- $jobservice := .Values.persistence.persistentVolumeClaim.jobservice -}} +{{- if and .Values.persistence.enabled (not $jobservice.existingClaim) (has "file" .Values.jobservice.jobLoggers) }} +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: {{ template "harbor.jobservice" . }} + annotations: + {{- range $key, $value := $jobservice.annotations }} + {{ $key }}: {{ $value | quote }} + {{- end }} + {{- if eq .Values.persistence.resourcePolicy "keep" }} + helm.sh/resource-policy: keep + {{- end }} + labels: +{{ include "harbor.labels" . | indent 4 }} + component: jobservice +spec: + accessModes: + - {{ $jobservice.accessMode }} + resources: + requests: + storage: {{ $jobservice.size }} + {{- if $jobservice.storageClass }} + {{- if eq "-" $jobservice.storageClass }} + storageClassName: "" + {{- else }} + storageClassName: {{ $jobservice.storageClass }} + {{- end }} + {{- end }} +{{- end }} diff --git a/ResearchOps/harbor/templates/jobservice/jobservice-secrets.yaml b/ResearchOps/harbor/templates/jobservice/jobservice-secrets.yaml new file mode 100644 index 0000000..dfd438c --- /dev/null +++ b/ResearchOps/harbor/templates/jobservice/jobservice-secrets.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Secret +metadata: + name: "{{ template "harbor.jobservice" . }}" + labels: +{{ include "harbor.labels" . | indent 4 }} +type: Opaque +data: + JOBSERVICE_SECRET: {{ .Values.jobservice.secret | default (randAlphaNum 16) | b64enc | quote }} + REGISTRY_CREDENTIAL_PASSWORD: {{ .Values.registry.credentials.password | b64enc | quote }} + {{- template "harbor.traceJaegerPassword" . }} \ No newline at end of file diff --git a/ResearchOps/harbor/templates/jobservice/jobservice-svc.yaml b/ResearchOps/harbor/templates/jobservice/jobservice-svc.yaml new file mode 100644 index 0000000..d2b7a47 --- /dev/null +++ b/ResearchOps/harbor/templates/jobservice/jobservice-svc.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Service +metadata: + name: "{{ template "harbor.jobservice" . }}" + labels: +{{ include "harbor.labels" . | indent 4 }} +spec: + ports: + - name: {{ ternary "https-jobservice" "http-jobservice" .Values.internalTLS.enabled }} + port: {{ template "harbor.jobservice.servicePort" . }} + targetPort: {{ template "harbor.jobservice.containerPort" . }} +{{- if .Values.metrics.enabled }} + - name: {{ template "harbor.metricsPortName" . }} + port: {{ .Values.metrics.jobservice.port }} +{{- end }} + selector: +{{ include "harbor.matchLabels" . | indent 4 }} + component: jobservice diff --git a/ResearchOps/harbor/templates/jobservice/jobservice-tls.yaml b/ResearchOps/harbor/templates/jobservice/jobservice-tls.yaml new file mode 100644 index 0000000..234cb39 --- /dev/null +++ b/ResearchOps/harbor/templates/jobservice/jobservice-tls.yaml @@ -0,0 +1,15 @@ +{{- if and .Values.internalTLS.enabled }} +{{- if eq .Values.internalTLS.certSource "manual" }} +apiVersion: v1 +kind: Secret +metadata: + name: "{{ template "harbor.internalTLS.jobservice.secretName" . }}" + labels: +{{ include "harbor.labels" . | indent 4 }} +type: kubernetes.io/tls +data: + ca.crt: {{ (required "The \"internalTLS.trustCa\" is required!" .Values.internalTLS.trustCa) | b64enc | quote }} + tls.crt: {{ (required "The \"internalTLS.jobservice.crt\" is required!" .Values.internalTLS.jobservice.crt) | b64enc | quote }} + tls.key: {{ (required "The \"internalTLS.jobservice.key\" is required!" .Values.internalTLS.jobservice.key) | b64enc | quote }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/ResearchOps/harbor/templates/metrics/metrics-svcmon.yaml b/ResearchOps/harbor/templates/metrics/metrics-svcmon.yaml new file mode 100644 index 0000000..ad85229 --- /dev/null +++ b/ResearchOps/harbor/templates/metrics/metrics-svcmon.yaml @@ -0,0 +1,28 @@ +{{- if and ( .Capabilities.APIVersions.Has "monitoring.coreos.com/v1" ) .Values.metrics.enabled .Values.metrics.serviceMonitor.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ template "harbor.fullname" . }} + labels: {{ include "harbor.labels" . | nindent 4 }} +{{- if .Values.metrics.serviceMonitor.additionalLabels }} +{{ toYaml .Values.metrics.serviceMonitor.additionalLabels | indent 4 }} +{{- end }} +spec: + jobLabel: app.kubernetes.io/name + endpoints: + - port: {{ template "harbor.metricsPortName" . }} + {{- if .Values.metrics.serviceMonitor.interval }} + interval: {{ .Values.metrics.serviceMonitor.interval }} + {{- end }} + honorLabels: true +{{- if .Values.metrics.serviceMonitor.metricRelabelings }} + metricRelabelings: +{{ tpl (toYaml .Values.metrics.serviceMonitor.metricRelabelings | indent 4) . }} +{{- end }} +{{- if .Values.metrics.serviceMonitor.relabelings }} + relabelings: +{{ toYaml .Values.metrics.serviceMonitor.relabelings | indent 4 }} +{{- end }} + selector: + matchLabels: {{ include "harbor.matchLabels" . | nindent 6 }} +{{- end }} diff --git a/ResearchOps/harbor/templates/nginx/configmap-http.yaml b/ResearchOps/harbor/templates/nginx/configmap-http.yaml new file mode 100644 index 0000000..3aa4263 --- /dev/null +++ b/ResearchOps/harbor/templates/nginx/configmap-http.yaml @@ -0,0 +1,148 @@ +{{- if and (ne .Values.expose.type "ingress") (not .Values.expose.tls.enabled) }} +{{- $scheme := (include "harbor.component.scheme" .) -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "harbor.nginx" . }} + labels: +{{ include "harbor.labels" . | indent 4 }} +data: + nginx.conf: |+ + worker_processes auto; + pid /tmp/nginx.pid; + + events { + worker_connections 3096; + use epoll; + multi_accept on; + } + + http { + client_body_temp_path /tmp/client_body_temp; + proxy_temp_path /tmp/proxy_temp; + fastcgi_temp_path /tmp/fastcgi_temp; + uwsgi_temp_path /tmp/uwsgi_temp; + scgi_temp_path /tmp/scgi_temp; + tcp_nodelay on; + + # this is necessary for us to be able to disable request buffering in all cases + proxy_http_version 1.1; + + upstream core { + server "{{ template "harbor.core" . }}:{{ template "harbor.core.servicePort" . }}"; + } + + upstream portal { + server {{ template "harbor.portal" . }}:{{ template "harbor.portal.servicePort" . }}; + } + + log_format timed_combined '[$time_local]:$remote_addr - ' + '"$request" $status $body_bytes_sent ' + '"$http_referer" "$http_user_agent" ' + '$request_time $upstream_response_time $pipe'; + + access_log /dev/stdout timed_combined; + + map $http_x_forwarded_proto $x_forwarded_proto { + default $http_x_forwarded_proto; + "" $scheme; + } + + server { + {{- if .Values.ipFamily.ipv4.enabled}} + listen 8080; + {{- end}} + {{- if .Values.ipFamily.ipv6.enabled }} + listen [::]:8080; + {{- end }} + server_tokens off; + # disable any limits to avoid HTTP 413 for large image uploads + client_max_body_size 0; + + # Add extra headers + add_header X-Frame-Options DENY; + add_header Content-Security-Policy "frame-ancestors 'none'"; + + location / { + proxy_pass {{ $scheme }}://portal/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $x_forwarded_proto; + + proxy_buffering off; + proxy_request_buffering off; + } + + location /api/ { + proxy_pass {{ $scheme }}://core/api/; + {{- if and .Values.internalTLS.enabled }} + proxy_ssl_verify off; + proxy_ssl_session_reuse on; + {{- end }} + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $x_forwarded_proto; + + proxy_buffering off; + proxy_request_buffering off; + } + + location /chartrepo/ { + proxy_pass {{ $scheme }}://core/chartrepo/; + {{- if and .Values.internalTLS.enabled }} + proxy_ssl_verify off; + proxy_ssl_session_reuse on; + {{- end }} + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $x_forwarded_proto; + + proxy_buffering off; + proxy_request_buffering off; + } + + location /c/ { + proxy_pass {{ $scheme }}://core/c/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $x_forwarded_proto; + + proxy_buffering off; + proxy_request_buffering off; + } + + location /v1/ { + return 404; + } + + location /v2/ { + proxy_pass {{ $scheme }}://core/v2/; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $x_forwarded_proto; + proxy_buffering off; + proxy_request_buffering off; + } + + location /service/ { + proxy_pass {{ $scheme }}://core/service/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $x_forwarded_proto; + + proxy_buffering off; + proxy_request_buffering off; + } + + location /service/notifications { + return 404; + } + } + } +{{- end }} diff --git a/ResearchOps/harbor/templates/nginx/configmap-https.yaml b/ResearchOps/harbor/templates/nginx/configmap-https.yaml new file mode 100644 index 0000000..91674cb --- /dev/null +++ b/ResearchOps/harbor/templates/nginx/configmap-https.yaml @@ -0,0 +1,227 @@ +{{- if and (ne .Values.expose.type "ingress") .Values.expose.tls.enabled }} +{{- $scheme := (include "harbor.component.scheme" .) -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "harbor.nginx" . }} + labels: +{{ include "harbor.labels" . | indent 4 }} +data: + nginx.conf: |+ + worker_processes auto; + pid /tmp/nginx.pid; + + events { + worker_connections 3096; + use epoll; + multi_accept on; + } + + http { + client_body_temp_path /tmp/client_body_temp; + proxy_temp_path /tmp/proxy_temp; + fastcgi_temp_path /tmp/fastcgi_temp; + uwsgi_temp_path /tmp/uwsgi_temp; + scgi_temp_path /tmp/scgi_temp; + tcp_nodelay on; + + # this is necessary for us to be able to disable request buffering in all cases + proxy_http_version 1.1; + + upstream core { + server "{{ template "harbor.core" . }}:{{ template "harbor.core.servicePort" . }}"; + } + + upstream portal { + server "{{ template "harbor.portal" . }}:{{ template "harbor.portal.servicePort" . }}"; + } + + {{- if .Values.notary.enabled }} + upstream notary-server { + server {{ template "harbor.notary-server" . }}:4443; + } + {{- end }} + + log_format timed_combined '[$time_local]:$remote_addr - ' + '"$request" $status $body_bytes_sent ' + '"$http_referer" "$http_user_agent" ' + '$request_time $upstream_response_time $pipe'; + + access_log /dev/stdout timed_combined; + + map $http_x_forwarded_proto $x_forwarded_proto { + default $http_x_forwarded_proto; + "" $scheme; + } + + {{- if .Values.notary.enabled }} + server { + {{- if .Values.ipFamily.ipv4.enabled }} + listen 4443 ssl; + {{- end}} + {{- if .Values.ipFamily.ipv6.enabled}} + listen [::]:4443 ssl; + {{- end }} + server_tokens off; + # ssl + ssl_certificate /etc/nginx/cert/tls.crt; + ssl_certificate_key /etc/nginx/cert/tls.key; + + # recommendations from https://raymii.org/s/tutorials/strong_ssl_security_on_nginx.html + ssl_protocols tlsv1.1 tlsv1.2; + ssl_ciphers '!aNULL:kECDH+AESGCM:ECDH+AESGCM:RSA+AESGCM:kECDH+AES:ECDH+AES:RSA+AES:'; + ssl_prefer_server_ciphers on; + ssl_session_cache shared:ssl:10m; + + # disable any limits to avoid http 413 for large image uploads + client_max_body_size 0; + + # required to avoid http 411: see issue #1486 (https://github.com/docker/docker/issues/1486) + chunked_transfer_encoding on; + + location /v2/ { + proxy_pass http://notary-server/v2/; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $x_forwarded_proto; + + proxy_buffering off; + proxy_request_buffering off; + } + } + {{- end }} + + server { + {{- if .Values.ipFamily.ipv4.enabled }} + listen 8443 ssl; + {{- end}} + {{- if .Values.ipFamily.ipv6.enabled }} + listen [::]:8443 ssl; + {{- end }} + # server_name harbordomain.com; + server_tokens off; + # SSL + ssl_certificate /etc/nginx/cert/tls.crt; + ssl_certificate_key /etc/nginx/cert/tls.key; + + # Recommendations from https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html + ssl_protocols TLSv1.1 TLSv1.2; + ssl_ciphers '!aNULL:kECDH+AESGCM:ECDH+AESGCM:RSA+AESGCM:kECDH+AES:ECDH+AES:RSA+AES:'; + ssl_prefer_server_ciphers on; + ssl_session_cache shared:SSL:10m; + + # disable any limits to avoid HTTP 413 for large image uploads + client_max_body_size 0; + + # required to avoid HTTP 411: see Issue #1486 (https://github.com/docker/docker/issues/1486) + chunked_transfer_encoding on; + + # Add extra headers + add_header Strict-Transport-Security "max-age=31536000; includeSubdomains; preload"; + add_header X-Frame-Options DENY; + add_header Content-Security-Policy "frame-ancestors 'none'"; + + location / { + proxy_pass {{ $scheme }}://portal/; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $x_forwarded_proto; + + proxy_cookie_path / "/; HttpOnly; Secure"; + + proxy_buffering off; + proxy_request_buffering off; + } + + location /api/ { + proxy_pass {{ $scheme }}://core/api/; + {{- if and .Values.internalTLS.enabled }} + proxy_ssl_verify off; + proxy_ssl_session_reuse on; + {{- end }} + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $x_forwarded_proto; + + proxy_cookie_path / "/; Secure"; + + proxy_buffering off; + proxy_request_buffering off; + } + + location /chartrepo/ { + proxy_pass {{ $scheme }}://core/chartrepo/; + {{- if and .Values.internalTLS.enabled }} + proxy_ssl_verify off; + proxy_ssl_session_reuse on; + {{- end }} + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $x_forwarded_proto; + + proxy_cookie_path / "/; Secure"; + + proxy_buffering off; + proxy_request_buffering off; + } + + location /c/ { + proxy_pass {{ $scheme }}://core/c/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $x_forwarded_proto; + + proxy_cookie_path / "/; Secure"; + + proxy_buffering off; + proxy_request_buffering off; + } + + location /v1/ { + return 404; + } + + location /v2/ { + proxy_pass {{ $scheme }}://core/v2/; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $x_forwarded_proto; + proxy_buffering off; + proxy_request_buffering off; + } + + location /service/ { + proxy_pass {{ $scheme }}://core/service/; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $x_forwarded_proto; + + proxy_cookie_path / "/; Secure"; + + proxy_buffering off; + proxy_request_buffering off; + } + + location /service/notifications { + return 404; + } + } + server { + {{- if .Values.ipFamily.ipv4.enabled }} + listen 8080; + {{- end}} + {{- if .Values.ipFamily.ipv6.enabled }} + listen [::]:8080; + {{- end}} + #server_name harbordomain.com; + return 301 https://$host$request_uri; + } + } +{{- end }} diff --git a/ResearchOps/harbor/templates/nginx/deployment.yaml b/ResearchOps/harbor/templates/nginx/deployment.yaml new file mode 100644 index 0000000..bc1de0a --- /dev/null +++ b/ResearchOps/harbor/templates/nginx/deployment.yaml @@ -0,0 +1,109 @@ +{{- if ne .Values.expose.type "ingress" }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "harbor.nginx" . }} + labels: +{{ include "harbor.labels" . | indent 4 }} + component: nginx +spec: + replicas: {{ .Values.nginx.replicas }} + revisionHistoryLimit: {{ .Values.nginx.revisionHistoryLimit }} + selector: + matchLabels: +{{ include "harbor.matchLabels" . | indent 6 }} + component: nginx + template: + metadata: + labels: +{{ include "harbor.labels" . | indent 8 }} + component: nginx + annotations: + {{- if not .Values.expose.tls.enabled }} + checksum/configmap: {{ include (print $.Template.BasePath "/nginx/configmap-http.yaml") . | sha256sum }} + {{- else }} + checksum/configmap: {{ include (print $.Template.BasePath "/nginx/configmap-https.yaml") . | sha256sum }} + {{- end }} + {{- if eq (include "harbor.autoGenCertForNginx" .) "true" }} + checksum/secret: {{ include (print $.Template.BasePath "/nginx/secret.yaml") . | sha256sum }} + {{- end }} +{{- if .Values.nginx.podAnnotations }} +{{ toYaml .Values.nginx.podAnnotations | indent 8 }} +{{- end }} + spec: +{{- if .Values.nginx.serviceAccountName }} + serviceAccountName: {{ .Values.nginx.serviceAccountName }} +{{- end }} + securityContext: + runAsUser: 10000 + fsGroup: 10000 + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + automountServiceAccountToken: {{ .Values.nginx.automountServiceAccountToken | default false }} + containers: + - name: nginx + image: "{{ .Values.nginx.image.repository }}:{{ .Values.nginx.image.tag }}" + imagePullPolicy: "{{ .Values.imagePullPolicy }}" + {{- $_ := set . "scheme" "HTTP" -}} + {{- $_ := set . "port" "8080" -}} + {{- if .Values.expose.tls.enabled }} + {{- $_ := set . "scheme" "HTTPS" -}} + {{- $_ := set . "port" "8443" -}} + {{- end }} + livenessProbe: + httpGet: + scheme: {{ .scheme }} + path: / + port: {{ .port }} + initialDelaySeconds: 300 + periodSeconds: 10 + readinessProbe: + httpGet: + scheme: {{ .scheme }} + path: / + port: {{ .port }} + initialDelaySeconds: 1 + periodSeconds: 10 +{{- if .Values.nginx.resources }} + resources: +{{ toYaml .Values.nginx.resources | indent 10 }} +{{- end }} + ports: + - containerPort: 8080 + - containerPort: 8443 + - containerPort: 4443 + volumeMounts: + - name: config + mountPath: /etc/nginx/nginx.conf + subPath: nginx.conf + {{- if .Values.expose.tls.enabled }} + - name: certificate + mountPath: /etc/nginx/cert + {{- end }} + volumes: + - name: config + configMap: + name: {{ template "harbor.nginx" . }} + {{- if .Values.expose.tls.enabled }} + - name: certificate + secret: + secretName: {{ template "harbor.tlsSecretForNginx" . }} + {{- end }} + {{- with .Values.nginx.nodeSelector }} + nodeSelector: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.nginx.affinity }} + affinity: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.nginx.tolerations }} + tolerations: +{{ toYaml . | indent 8 }} + {{- end }} + {{- if .Values.nginx.priorityClassName }} + priorityClassName: {{ .Values.nginx.priorityClassName }} + {{- end }} +{{- end }} diff --git a/ResearchOps/harbor/templates/nginx/secret.yaml b/ResearchOps/harbor/templates/nginx/secret.yaml new file mode 100644 index 0000000..c819c55 --- /dev/null +++ b/ResearchOps/harbor/templates/nginx/secret.yaml @@ -0,0 +1,23 @@ +{{- if eq (include "harbor.autoGenCertForNginx" .) "true" }} +{{- $ca := genCA "harbor-ca" 365 }} +{{- $cn := (required "The \"expose.tls.auto.commonName\" is required!" .Values.expose.tls.auto.commonName) }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "harbor.nginx" . }} + labels: +{{ include "harbor.labels" . | indent 4 }} +type: Opaque +data: + {{- if regexMatch `^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$` $cn }} + {{- $cert := genSignedCert $cn (list $cn) nil 365 $ca }} + tls.crt: {{ $cert.Cert | b64enc | quote }} + tls.key: {{ $cert.Key | b64enc | quote }} + ca.crt: {{ $ca.Cert | b64enc | quote }} + {{- else }} + {{- $cert := genSignedCert $cn nil (list $cn) 365 $ca }} + tls.crt: {{ $cert.Cert | b64enc | quote }} + tls.key: {{ $cert.Key | b64enc | quote }} + ca.crt: {{ $ca.Cert | b64enc | quote }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/ResearchOps/harbor/templates/nginx/service.yaml b/ResearchOps/harbor/templates/nginx/service.yaml new file mode 100644 index 0000000..df4da09 --- /dev/null +++ b/ResearchOps/harbor/templates/nginx/service.yaml @@ -0,0 +1,96 @@ +{{- if or (eq .Values.expose.type "clusterIP") (eq .Values.expose.type "nodePort") (eq .Values.expose.type "loadBalancer") }} +apiVersion: v1 +kind: Service +metadata: +{{- if eq .Values.expose.type "clusterIP" }} +{{- $clusterIP := .Values.expose.clusterIP }} + name: {{ $clusterIP.name }} + labels: +{{ include "harbor.labels" . | indent 4 }} +{{- with $clusterIP.annotations }} + annotations: + {{- toYaml . | nindent 4 }} +{{- end }} +spec: + type: ClusterIP + ports: + - name: http + port: {{ $clusterIP.ports.httpPort }} + targetPort: 8080 + {{- if .Values.expose.tls.enabled }} + - name: https + port: {{ $clusterIP.ports.httpsPort }} + targetPort: 8443 + {{- end }} + {{- if .Values.notary.enabled }} + - name: notary + port: {{ $clusterIP.ports.notaryPort }} + targetPort: 4443 + {{- end }} +{{- else if eq .Values.expose.type "nodePort" }} +{{- $nodePort := .Values.expose.nodePort }} + name: {{ $nodePort.name }} + labels: +{{ include "harbor.labels" . | indent 4 }} +spec: + type: NodePort + ports: + - name: http + port: {{ $nodePort.ports.http.port }} + targetPort: 8080 + {{- if $nodePort.ports.http.nodePort }} + nodePort: {{ $nodePort.ports.http.nodePort }} + {{- end }} + {{- if .Values.expose.tls.enabled }} + - name: https + port: {{ $nodePort.ports.https.port }} + targetPort: 8443 + {{- if $nodePort.ports.https.nodePort }} + nodePort: {{ $nodePort.ports.https.nodePort }} + {{- end }} + {{- end }} + {{- if .Values.notary.enabled }} + - name: notary + port: {{ $nodePort.ports.notary.port }} + targetPort: 4443 + {{- if $nodePort.ports.notary.nodePort }} + nodePort: {{ $nodePort.ports.notary.nodePort }} + {{- end }} + {{- end }} +{{- else if eq .Values.expose.type "loadBalancer" }} +{{- $loadBalancer := .Values.expose.loadBalancer }} + name: {{ $loadBalancer.name }} + labels: +{{ include "harbor.labels" . | indent 4 }} +{{- with $loadBalancer.annotations }} + annotations: + {{- toYaml . | nindent 4 }} +{{- end }} +spec: + type: LoadBalancer + {{- with $loadBalancer.sourceRanges }} + loadBalancerSourceRanges: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- if $loadBalancer.IP }} + loadBalancerIP: {{ $loadBalancer.IP }} + {{- end }} + ports: + - name: http + port: {{ $loadBalancer.ports.httpPort }} + targetPort: 8080 + {{- if .Values.expose.tls.enabled }} + - name: https + port: {{ $loadBalancer.ports.httpsPort }} + targetPort: 8443 + {{- end }} + {{- if .Values.notary.enabled }} + - name: notary + port: {{ $loadBalancer.ports.notaryPort }} + targetPort: 4443 + {{- end }} +{{- end }} + selector: +{{ include "harbor.matchLabels" . | indent 4 }} + component: nginx +{{- end }} diff --git a/ResearchOps/harbor/templates/notary/notary-secret.yaml b/ResearchOps/harbor/templates/notary/notary-secret.yaml new file mode 100644 index 0000000..dc9f619 --- /dev/null +++ b/ResearchOps/harbor/templates/notary/notary-secret.yaml @@ -0,0 +1,20 @@ +{{- if and .Values.notary.enabled }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "harbor.notary-server" . }} + labels: +{{ include "harbor.labels" . | indent 4 }} + component: notary +type: Opaque +data: + {{- if not .Values.notary.secretName }} + {{- $ca := genCA "harbor-notary-ca" 365 }} + {{- $cert := genSignedCert (include "harbor.notary-signer" .) nil (list (include "harbor.notary-signer" .)) 365 $ca }} + ca.crt: {{ $ca.Cert | b64enc | quote }} + tls.crt: {{ $cert.Cert | b64enc | quote }} + tls.key: {{ $cert.Key | b64enc | quote }} + {{- end }} + server.json: {{ tpl (.Files.Get "conf/notary-server.json") . | b64enc }} + signer.json: {{ tpl (.Files.Get "conf/notary-signer.json") . | b64enc }} +{{- end }} diff --git a/ResearchOps/harbor/templates/notary/notary-server.yaml b/ResearchOps/harbor/templates/notary/notary-server.yaml new file mode 100644 index 0000000..753f2ec --- /dev/null +++ b/ResearchOps/harbor/templates/notary/notary-server.yaml @@ -0,0 +1,108 @@ +{{ if .Values.notary.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "harbor.notary-server" . }} + labels: +{{ include "harbor.labels" . | indent 4 }} + component: notary-server +spec: + replicas: {{ .Values.notary.server.replicas }} + selector: + matchLabels: +{{ include "harbor.matchLabels" . | indent 6 }} + component: notary-server + template: + metadata: + labels: +{{ include "harbor.labels" . | indent 8 }} + component: notary-server + annotations: + checksum/secret: {{ include (print $.Template.BasePath "/notary/notary-secret.yaml") . | sha256sum }} + checksum/secret-core: {{ include (print $.Template.BasePath "/core/core-secret.yaml") . | sha256sum }} +{{- if .Values.notary.server.podAnnotations }} +{{ toYaml .Values.notary.server.podAnnotations | indent 8 }} +{{- end }} + spec: + securityContext: + runAsUser: 10000 + fsGroup: 10000 +{{- if .Values.notary.server.serviceAccountName }} + serviceAccountName: {{ .Values.notary.server.serviceAccountName }} +{{- end -}} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + automountServiceAccountToken: {{ .Values.notary.server.automountServiceAccountToken | default false }} + containers: + - name: notary-server + image: {{ .Values.notary.server.image.repository }}:{{ .Values.notary.server.image.tag }} + imagePullPolicy: {{ .Values.imagePullPolicy }} + livenessProbe: + httpGet: + path: /_notary_server/health + scheme: "HTTP" + port: 4443 + initialDelaySeconds: 300 + periodSeconds: 10 + readinessProbe: + httpGet: + path: /_notary_server/health + scheme: "HTTP" + port: 4443 + initialDelaySeconds: 20 + periodSeconds: 10 +{{- if .Values.notary.server.resources }} + resources: +{{ toYaml .Values.notary.server.resources | indent 10 }} +{{- end }} + env: + - name: MIGRATIONS_PATH + value: migrations/server/postgresql + - name: DB_URL + value: {{ template "harbor.database.notaryServer" . }} + volumeMounts: + - name: config + mountPath: /etc/notary/server-config.postgres.json + subPath: server.json + - name: token-service-certificate + mountPath: /root.crt + subPath: tls.crt + - name: signer-certificate + mountPath: /etc/ssl/notary/ca.crt + subPath: ca.crt + volumes: + - name: config + secret: + secretName: "{{ template "harbor.notary-server" . }}" + - name: token-service-certificate + secret: + {{- if .Values.core.secretName }} + secretName: {{ .Values.core.secretName }} + {{- else }} + secretName: {{ template "harbor.core" . }} + {{- end }} + - name: signer-certificate + secret: + {{- if .Values.notary.secretName }} + secretName: {{ .Values.notary.secretName }} + {{- else }} + secretName: {{ template "harbor.notary-server" . }} + {{- end }} + {{- with .Values.notary.server.nodeSelector }} + nodeSelector: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.notary.server.affinity }} + affinity: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.notary.server.tolerations }} + tolerations: +{{ toYaml . | indent 8 }} + {{- end }} + {{- if .Values.notary.server.priorityClassName }} + priorityClassName: {{ .Values.notary.server.priorityClassName }} + {{- end }} +{{ end }} diff --git a/ResearchOps/harbor/templates/notary/notary-signer.yaml b/ResearchOps/harbor/templates/notary/notary-signer.yaml new file mode 100644 index 0000000..15987b8 --- /dev/null +++ b/ResearchOps/harbor/templates/notary/notary-signer.yaml @@ -0,0 +1,102 @@ +{{ if .Values.notary.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "harbor.notary-signer" . }} + labels: +{{ include "harbor.labels" . | indent 4 }} + component: notary-signer +spec: + replicas: {{ .Values.notary.signer.replicas }} + selector: + matchLabels: +{{ include "harbor.matchLabels" . | indent 6 }} + component: notary-signer + template: + metadata: + labels: +{{ include "harbor.labels" . | indent 8 }} + component: notary-signer + annotations: + checksum/secret: {{ include (print $.Template.BasePath "/notary/notary-secret.yaml") . | sha256sum }} +{{- if .Values.notary.signer.podAnnotations }} +{{ toYaml .Values.notary.signer.podAnnotations | indent 8 }} +{{- end }} + spec: + securityContext: + runAsUser: 10000 + fsGroup: 10000 +{{- if .Values.notary.signer.serviceAccountName }} + serviceAccountName: {{ .Values.notary.signer.serviceAccountName }} +{{- end -}} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + automountServiceAccountToken: {{ .Values.notary.signer.automountServiceAccountToken | default false }} + containers: + - name: notary-signer + image: {{ .Values.notary.signer.image.repository }}:{{ .Values.notary.signer.image.tag }} + imagePullPolicy: {{ .Values.imagePullPolicy }} + livenessProbe: + httpGet: + path: / + scheme: "HTTPS" + port: 7899 + initialDelaySeconds: 300 + periodSeconds: 10 + readinessProbe: + httpGet: + path: / + scheme: "HTTPS" + port: 7899 + initialDelaySeconds: 20 + periodSeconds: 10 +{{- if .Values.notary.signer.resources }} + resources: +{{ toYaml .Values.notary.signer.resources | indent 10 }} +{{- end }} + env: + - name: MIGRATIONS_PATH + value: migrations/signer/postgresql + - name: DB_URL + value: {{ template "harbor.database.notarySigner" . }} + - name: NOTARY_SIGNER_DEFAULTALIAS + value: defaultalias + volumeMounts: + - name: config + mountPath: /etc/notary/signer-config.postgres.json + subPath: signer.json + - name: signer-certificate + mountPath: /etc/ssl/notary/tls.crt + subPath: tls.crt + - name: signer-certificate + mountPath: /etc/ssl/notary/tls.key + subPath: tls.key + volumes: + - name: config + secret: + secretName: "{{ template "harbor.notary-server" . }}" + - name: signer-certificate + secret: + {{- if .Values.notary.secretName }} + secretName: {{ .Values.notary.secretName }} + {{- else }} + secretName: {{ template "harbor.notary-server" . }} + {{- end }} + {{- with .Values.notary.signer.nodeSelector }} + nodeSelector: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.notary.signer.affinity }} + affinity: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.notary.signer.tolerations }} + tolerations: +{{ toYaml . | indent 8 }} + {{- end }} + {{- if .Values.notary.signer.priorityClassName }} + priorityClassName: {{ .Values.notary.signer.priorityClassName }} + {{- end }} +{{ end }} diff --git a/ResearchOps/harbor/templates/notary/notary-svc.yaml b/ResearchOps/harbor/templates/notary/notary-svc.yaml new file mode 100644 index 0000000..f02ba3c --- /dev/null +++ b/ResearchOps/harbor/templates/notary/notary-svc.yaml @@ -0,0 +1,31 @@ +{{ if .Values.notary.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "harbor.notary-server" . }} + labels: +{{ include "harbor.labels" . | indent 4 }} +spec: +{{- if (eq .Values.expose.ingress.controller "gce") }} + type: NodePort +{{- end }} + ports: + - port: 4443 + selector: +{{ include "harbor.matchLabels" . | indent 4 }} + component: notary-server + +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ template "harbor.notary-signer" . }} + labels: +{{ include "harbor.labels" . | indent 4 }} +spec: + ports: + - port: 7899 + selector: +{{ include "harbor.matchLabels" . | indent 4 }} + component: notary-signer +{{ end }} \ No newline at end of file diff --git a/ResearchOps/harbor/templates/portal/configmap.yaml b/ResearchOps/harbor/templates/portal/configmap.yaml new file mode 100644 index 0000000..6ffed32 --- /dev/null +++ b/ResearchOps/harbor/templates/portal/configmap.yaml @@ -0,0 +1,60 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: "{{ template "harbor.portal" . }}" + labels: +{{ include "harbor.labels" . | indent 4 }} +data: + nginx.conf: |+ + worker_processes auto; + pid /tmp/nginx.pid; + events { + worker_connections 1024; + } + http { + client_body_temp_path /tmp/client_body_temp; + proxy_temp_path /tmp/proxy_temp; + fastcgi_temp_path /tmp/fastcgi_temp; + uwsgi_temp_path /tmp/uwsgi_temp; + scgi_temp_path /tmp/scgi_temp; + server { + {{- if .Values.internalTLS.enabled }} + {{- if .Values.ipFamily.ipv4.enabled}} + listen {{ template "harbor.portal.containerPort" . }} ssl; + {{- end }} + {{- if .Values.ipFamily.ipv6.enabled}} + listen [::]:{{ template "harbor.portal.containerPort" . }} ssl; + {{- end }} + # SSL + ssl_certificate /etc/harbor/ssl/portal/tls.crt; + ssl_certificate_key /etc/harbor/ssl/portal/tls.key; + + # Recommendations from https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html + ssl_protocols TLSv1.2; + ssl_ciphers '!aNULL:kECDH+AESGCM:ECDH+AESGCM:RSA+AESGCM:kECDH+AES:ECDH+AES:RSA+AES:'; + ssl_prefer_server_ciphers on; + ssl_session_cache shared:SSL:10m; + {{- else }} + {{- if .Values.ipFamily.ipv4.enabled }} + listen {{ template "harbor.portal.containerPort" . }}; + {{- end }} + {{- if .Values.ipFamily.ipv6.enabled}} + listen [::]:{{ template "harbor.portal.containerPort" . }}; + {{- end }} + {{- end }} + server_name localhost; + root /usr/share/nginx/html; + index index.html index.htm; + include /etc/nginx/mime.types; + gzip on; + gzip_min_length 1000; + gzip_proxied expired no-cache no-store private auth; + gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript; + location / { + try_files $uri $uri/ /index.html; + } + location = /index.html { + add_header Cache-Control "no-store, no-cache, must-revalidate"; + } + } + } diff --git a/ResearchOps/harbor/templates/portal/deployment.yaml b/ResearchOps/harbor/templates/portal/deployment.yaml new file mode 100644 index 0000000..934dc56 --- /dev/null +++ b/ResearchOps/harbor/templates/portal/deployment.yaml @@ -0,0 +1,96 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: "{{ template "harbor.portal" . }}" + labels: +{{ include "harbor.labels" . | indent 4 }} + component: portal +spec: + replicas: {{ .Values.portal.replicas }} + revisionHistoryLimit: {{ .Values.portal.revisionHistoryLimit }} + selector: + matchLabels: +{{ include "harbor.matchLabels" . | indent 6 }} + component: portal + template: + metadata: + labels: +{{ include "harbor.matchLabels" . | indent 8 }} + component: portal + annotations: +{{- if and .Values.internalTLS.enabled (eq .Values.internalTLS.certSource "auto") }} + checksum/tls: {{ include (print $.Template.BasePath "/internal/auto-tls.yaml") . | sha256sum }} +{{- else if and .Values.internalTLS.enabled (eq .Values.internalTLS.certSource "manual") }} + checksum/tls: {{ include (print $.Template.BasePath "/portal/tls.yaml") . | sha256sum }} +{{- end }} +{{- if .Values.portal.podAnnotations }} +{{ toYaml .Values.portal.podAnnotations | indent 8 }} +{{- end }} + spec: + securityContext: + runAsUser: 10000 + fsGroup: 10000 + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- if .Values.portal.serviceAccountName }} + serviceAccountName: {{ .Values.portal.serviceAccountName }} +{{- end }} + automountServiceAccountToken: {{ .Values.portal.automountServiceAccountToken | default false }} + containers: + - name: portal + image: {{ .Values.portal.image.repository }}:{{ .Values.portal.image.tag }} + imagePullPolicy: {{ .Values.imagePullPolicy }} +{{- if .Values.portal.resources }} + resources: +{{ toYaml .Values.portal.resources | indent 10 }} +{{- end }} + livenessProbe: + httpGet: + path: / + scheme: {{ include "harbor.component.scheme" . | upper }} + port: {{ template "harbor.portal.containerPort" . }} + initialDelaySeconds: 300 + periodSeconds: 10 + readinessProbe: + httpGet: + path: / + scheme: {{ include "harbor.component.scheme" . | upper }} + port: {{ template "harbor.portal.containerPort" . }} + initialDelaySeconds: 1 + periodSeconds: 10 + ports: + - containerPort: {{ template "harbor.portal.containerPort" . }} + volumeMounts: + - name: portal-config + mountPath: /etc/nginx/nginx.conf + subPath: nginx.conf + {{- if .Values.internalTLS.enabled }} + - name: portal-internal-certs + mountPath: /etc/harbor/ssl/portal + {{- end }} + volumes: + - name: portal-config + configMap: + name: "{{ template "harbor.portal" . }}" + {{- if .Values.internalTLS.enabled }} + - name: portal-internal-certs + secret: + secretName: {{ template "harbor.internalTLS.portal.secretName" . }} + {{- end }} + {{- with .Values.portal.nodeSelector }} + nodeSelector: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.portal.affinity }} + affinity: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.portal.tolerations }} + tolerations: +{{ toYaml . | indent 8 }} + {{- end }} + {{- if .Values.portal.priorityClassName }} + priorityClassName: {{ .Values.portal.priorityClassName }} + {{- end }} diff --git a/ResearchOps/harbor/templates/portal/service.yaml b/ResearchOps/harbor/templates/portal/service.yaml new file mode 100644 index 0000000..c0a3a20 --- /dev/null +++ b/ResearchOps/harbor/templates/portal/service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: "{{ template "harbor.portal" . }}" + labels: +{{ include "harbor.labels" . | indent 4 }} +spec: +{{- if (eq .Values.expose.ingress.controller "gce") }} + type: NodePort +{{- end }} + ports: + - port: {{ template "harbor.portal.servicePort" . }} + targetPort: {{ template "harbor.portal.containerPort" . }} + selector: +{{ include "harbor.matchLabels" . | indent 4 }} + component: portal diff --git a/ResearchOps/harbor/templates/portal/tls.yaml b/ResearchOps/harbor/templates/portal/tls.yaml new file mode 100644 index 0000000..de63f4e --- /dev/null +++ b/ResearchOps/harbor/templates/portal/tls.yaml @@ -0,0 +1,15 @@ +{{- if and .Values.internalTLS.enabled }} +{{- if eq .Values.internalTLS.certSource "manual" }} +apiVersion: v1 +kind: Secret +metadata: + name: "{{ template "harbor.internalTLS.portal.secretName" . }}" + labels: +{{ include "harbor.labels" . | indent 4 }} +type: kubernetes.io/tls +data: + ca.crt: {{ (required "The \"internalTLS.trustCa\" is required!" .Values.internalTLS.trustCa) | b64enc | quote }} + tls.crt: {{ (required "The \"internalTLS.portal.crt\" is required!" .Values.internalTLS.portal.crt) | b64enc | quote }} + tls.key: {{ (required "The \"internalTLS.portal.key\" is required!" .Values.internalTLS.portal.key) | b64enc | quote }} +{{- end }} +{{- end }} diff --git a/ResearchOps/harbor/templates/redis/service.yaml b/ResearchOps/harbor/templates/redis/service.yaml new file mode 100644 index 0000000..79c95c3 --- /dev/null +++ b/ResearchOps/harbor/templates/redis/service.yaml @@ -0,0 +1,14 @@ +{{- if eq .Values.redis.type "internal" -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "harbor.redis" . }} + labels: +{{ include "harbor.labels" . | indent 4 }} +spec: + ports: + - port: 6379 + selector: +{{ include "harbor.matchLabels" . | indent 4 }} + component: redis +{{- end -}} \ No newline at end of file diff --git a/ResearchOps/harbor/templates/redis/statefulset.yaml b/ResearchOps/harbor/templates/redis/statefulset.yaml new file mode 100644 index 0000000..74b7581 --- /dev/null +++ b/ResearchOps/harbor/templates/redis/statefulset.yaml @@ -0,0 +1,109 @@ +{{- if eq .Values.redis.type "internal" -}} +{{- $redis := .Values.persistence.persistentVolumeClaim.redis -}} +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ template "harbor.redis" . }} + labels: +{{ include "harbor.labels" . | indent 4 }} + component: redis +spec: + replicas: 1 + serviceName: {{ template "harbor.redis" . }} + selector: + matchLabels: +{{ include "harbor.matchLabels" . | indent 6 }} + component: redis + template: + metadata: + labels: +{{ include "harbor.labels" . | indent 8 }} + component: redis +{{- if .Values.redis.podAnnotations }} + annotations: +{{ toYaml .Values.redis.podAnnotations | indent 8 }} +{{- end }} + spec: + securityContext: + runAsUser: 999 + fsGroup: 999 +{{- if .Values.redis.internal.serviceAccountName }} + serviceAccountName: {{ .Values.redis.internal.serviceAccountName }} +{{- end -}} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + automountServiceAccountToken: {{ .Values.redis.internal.automountServiceAccountToken | default false }} + terminationGracePeriodSeconds: 120 + containers: + - name: redis + image: {{ .Values.redis.internal.image.repository }}:{{ .Values.redis.internal.image.tag }} + imagePullPolicy: {{ .Values.imagePullPolicy }} + livenessProbe: + tcpSocket: + port: 6379 + initialDelaySeconds: 300 + periodSeconds: 10 + readinessProbe: + tcpSocket: + port: 6379 + initialDelaySeconds: 1 + periodSeconds: 10 +{{- if .Values.redis.internal.resources }} + resources: +{{ toYaml .Values.redis.internal.resources | indent 10 }} +{{- end }} + volumeMounts: + - name: data + mountPath: /var/lib/redis + subPath: {{ $redis.subPath }} + {{- if not .Values.persistence.enabled }} + volumes: + - name: data + emptyDir: {} + {{- else if $redis.existingClaim }} + volumes: + - name: data + persistentVolumeClaim: + claimName: {{ $redis.existingClaim }} + {{- end -}} + {{- with .Values.redis.internal.nodeSelector }} + nodeSelector: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.redis.internal.affinity }} + affinity: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.redis.internal.tolerations }} + tolerations: +{{ toYaml . | indent 8 }} + {{- end }} + {{- if .Values.redis.internal.priorityClassName }} + priorityClassName: {{ .Values.redis.internal.priorityClassName }} + {{- end }} + {{- if and .Values.persistence.enabled (not $redis.existingClaim) }} + volumeClaimTemplates: + - metadata: + name: data + labels: +{{ include "harbor.labels" . | indent 8 }} + annotations: + {{- range $key, $value := $redis.annotations }} + {{ $key }}: {{ $value | quote }} + {{- end }} + spec: + accessModes: [{{ $redis.accessMode | quote }}] + {{- if $redis.storageClass }} + {{- if (eq "-" $redis.storageClass) }} + storageClassName: "" + {{- else }} + storageClassName: "{{ $redis.storageClass }}" + {{- end }} + {{- end }} + resources: + requests: + storage: {{ $redis.size | quote }} + {{- end -}} + {{- end -}} diff --git a/ResearchOps/harbor/templates/registry/registry-cm.yaml b/ResearchOps/harbor/templates/registry/registry-cm.yaml new file mode 100644 index 0000000..8af7969 --- /dev/null +++ b/ResearchOps/harbor/templates/registry/registry-cm.yaml @@ -0,0 +1,244 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: "{{ template "harbor.registry" . }}" + labels: +{{ include "harbor.labels" . | indent 4 }} +data: + config.yml: |+ + version: 0.1 + log: + {{- if eq .Values.logLevel "warning" }} + level: warn + {{- else if eq .Values.logLevel "fatal" }} + level: error + {{- else }} + level: {{ .Values.logLevel }} + {{- end }} + fields: + service: registry + storage: + {{- $storage := .Values.persistence.imageChartStorage }} + {{- $type := $storage.type }} + {{- if eq $type "filesystem" }} + filesystem: + rootdirectory: {{ $storage.filesystem.rootdirectory }} + {{- if $storage.filesystem.maxthreads }} + maxthreads: {{ $storage.filesystem.maxthreads }} + {{- end }} + {{- else if eq $type "azure" }} + azure: + accountname: {{ $storage.azure.accountname }} + container: {{ $storage.azure.container }} + {{- if $storage.azure.realm }} + realm: {{ $storage.azure.realm }} + {{- end }} + {{- else if eq $type "gcs" }} + gcs: + bucket: {{ $storage.gcs.bucket }} + keyfile: /etc/registry/gcs-key.json + {{- if $storage.gcs.rootdirectory }} + rootdirectory: {{ $storage.gcs.rootdirectory }} + {{- end }} + {{- if $storage.gcs.chunksize }} + chunksize: {{ $storage.gcs.chunksize }} + {{- end }} + {{- else if eq $type "s3" }} + s3: + region: {{ $storage.s3.region }} + bucket: {{ $storage.s3.bucket }} + {{- if $storage.s3.regionendpoint }} + regionendpoint: {{ $storage.s3.regionendpoint }} + {{- end }} + {{- if $storage.s3.encrypt }} + encrypt: {{ $storage.s3.encrypt }} + {{- end }} + {{- if $storage.s3.keyid }} + keyid: {{ $storage.s3.keyid }} + {{- end }} + {{- if $storage.s3.secure }} + secure: {{ $storage.s3.secure }} + {{- end }} + {{- if and $storage.s3.secure $storage.s3.skipverify }} + skipverify: {{ $storage.s3.skipverify }} + {{- end }} + {{- if $storage.s3.v4auth }} + v4auth: {{ $storage.s3.v4auth }} + {{- end }} + {{- if $storage.s3.chunksize }} + chunksize: {{ $storage.s3.chunksize }} + {{- end }} + {{- if $storage.s3.rootdirectory }} + rootdirectory: {{ $storage.s3.rootdirectory }} + {{- end }} + {{- if $storage.s3.storageclass }} + storageclass: {{ $storage.s3.storageclass }} + {{- end }} + {{- if $storage.s3.multipartcopychunksize }} + multipartcopychunksize: {{ $storage.s3.multipartcopychunksize }} + {{- end }} + {{- if $storage.s3.multipartcopymaxconcurrency }} + multipartcopymaxconcurrency: {{ $storage.s3.multipartcopymaxconcurrency }} + {{- end }} + {{- if $storage.s3.multipartcopythresholdsize }} + multipartcopythresholdsize: {{ $storage.s3.multipartcopythresholdsize }} + {{- end }} + {{- else if eq $type "swift" }} + swift: + authurl: {{ $storage.swift.authurl }} + username: {{ $storage.swift.username }} + container: {{ $storage.swift.container }} + {{- if $storage.swift.region }} + region: {{ $storage.swift.region }} + {{- end }} + {{- if $storage.swift.tenant }} + tenant: {{ $storage.swift.tenant }} + {{- end }} + {{- if $storage.swift.tenantid }} + tenantid: {{ $storage.swift.tenantid }} + {{- end }} + {{- if $storage.swift.domain }} + domain: {{ $storage.swift.domain }} + {{- end }} + {{- if $storage.swift.domainid }} + domainid: {{ $storage.swift.domainid }} + {{- end }} + {{- if $storage.swift.trustid }} + trustid: {{ $storage.swift.trustid }} + {{- end }} + {{- if $storage.swift.insecureskipverify }} + insecureskipverify: {{ $storage.swift.insecureskipverify }} + {{- end }} + {{- if $storage.swift.chunksize }} + chunksize: {{ $storage.swift.chunksize }} + {{- end }} + {{- if $storage.swift.prefix }} + prefix: {{ $storage.swift.prefix }} + {{- end }} + {{- if $storage.swift.authversion }} + authversion: {{ $storage.swift.authversion }} + {{- end }} + {{- if $storage.swift.endpointtype }} + endpointtype: {{ $storage.swift.endpointtype }} + {{- end }} + {{- if $storage.swift.tempurlcontainerkey }} + tempurlcontainerkey: {{ $storage.swift.tempurlcontainerkey }} + {{- end }} + {{- if $storage.swift.tempurlmethods }} + tempurlmethods: {{ $storage.swift.tempurlmethods }} + {{- end }} + {{- else if eq $type "oss" }} + oss: + accesskeyid: {{ $storage.oss.accesskeyid }} + region: {{ $storage.oss.region }} + bucket: {{ $storage.oss.bucket }} + {{- if $storage.oss.endpoint }} + endpoint: {{ $storage.oss.bucket }}.{{ $storage.oss.endpoint }} + {{- end }} + {{- if $storage.oss.internal }} + internal: {{ $storage.oss.internal }} + {{- end }} + {{- if $storage.oss.encrypt }} + encrypt: {{ $storage.oss.encrypt }} + {{- end }} + {{- if $storage.oss.secure }} + secure: {{ $storage.oss.secure }} + {{- end }} + {{- if $storage.oss.chunksize }} + chunksize: {{ $storage.oss.chunksize }} + {{- end }} + {{- if $storage.oss.rootdirectory }} + rootdirectory: {{ $storage.oss.rootdirectory }} + {{- end }} + {{- end }} + cache: + layerinfo: redis + maintenance: + uploadpurging: + {{- if .Values.registry.upload_purging.enabled }} + enabled: true + age: {{ .Values.registry.upload_purging.age }} + interval: {{ .Values.registry.upload_purging.interval }} + dryrun: {{ .Values.registry.upload_purging.dryrun }} + {{- else }} + enabled: false + {{- end }} + delete: + enabled: true + redirect: + disable: {{ $storage.disableredirect }} + redis: + addr: {{ template "harbor.redis.addr" . }} + {{- if eq "redis+sentinel" (include "harbor.redis.scheme" .) }} + sentinelMasterSet: {{ template "harbor.redis.masterSet" . }} + {{- end }} + db: {{ template "harbor.redis.dbForRegistry" . }} + {{- if not (eq (include "harbor.redis.password" .) "") }} + password: {{ template "harbor.redis.password" . }} + {{- end }} + readtimeout: 10s + writetimeout: 10s + dialtimeout: 10s + pool: + maxidle: 100 + maxactive: 500 + idletimeout: 60s + http: + addr: :{{ template "harbor.registry.containerPort" . }} + relativeurls: {{ .Values.registry.relativeurls }} + {{- if .Values.internalTLS.enabled }} + tls: + certificate: /etc/harbor/ssl/registry/tls.crt + key: /etc/harbor/ssl/registry/tls.key + minimumtls: tls1.2 + {{- end }} + # set via environment variable + # secret: placeholder + debug: + {{- if .Values.metrics.enabled}} + addr: :{{ .Values.metrics.registry.port }} + prometheus: + enabled: true + path: {{ .Values.metrics.registry.path }} + {{- else }} + addr: localhost:5001 + {{- end }} + auth: + htpasswd: + realm: harbor-registry-basic-realm + path: /etc/registry/passwd + validation: + disabled: true + compatibility: + schema1: + enabled: true + + {{- if .Values.registry.middleware.enabled }} + {{- $middleware := .Values.registry.middleware }} + {{- $middlewareType := $middleware.type }} + {{- if eq $middlewareType "cloudFront" }} + middleware: + storage: + - name: cloudfront + options: + baseurl: {{ $middleware.cloudFront.baseurl }} + privatekey: /etc/registry/pk.pem + keypairid: {{ $middleware.cloudFront.keypairid }} + duration: {{ $middleware.cloudFront.duration }} + ipfilteredby: {{ $middleware.cloudFront.ipfilteredby }} + {{- end }} + {{- end }} + ctl-config.yml: |+ + --- + {{- if .Values.internalTLS.enabled }} + protocol: "https" + port: 8443 + https_config: + cert: "/etc/harbor/ssl/registry/tls.crt" + key: "/etc/harbor/ssl/registry/tls.key" + {{- else }} + protocol: "http" + port: 8080 + {{- end }} + log_level: {{ .Values.logLevel }} + registry_config: "/etc/registry/config.yml" diff --git a/ResearchOps/harbor/templates/registry/registry-dpl.yaml b/ResearchOps/harbor/templates/registry/registry-dpl.yaml new file mode 100644 index 0000000..80f0fce --- /dev/null +++ b/ResearchOps/harbor/templates/registry/registry-dpl.yaml @@ -0,0 +1,281 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: "{{ template "harbor.registry" . }}" + labels: +{{ include "harbor.labels" . | indent 4 }} + component: registry +spec: + replicas: {{ .Values.registry.replicas }} + revisionHistoryLimit: {{ .Values.registry.revisionHistoryLimit }} + strategy: + type: {{ .Values.updateStrategy.type }} + {{- if eq .Values.updateStrategy.type "Recreate" }} + rollingUpdate: null + {{- end }} + selector: + matchLabels: +{{ include "harbor.matchLabels" . | indent 6 }} + component: registry + template: + metadata: + labels: +{{ include "harbor.labels" . | indent 8 }} + component: registry + annotations: + checksum/configmap: {{ include (print $.Template.BasePath "/registry/registry-cm.yaml") . | sha256sum }} + checksum/secret: {{ include (print $.Template.BasePath "/registry/registry-secret.yaml") . | sha256sum }} + checksum/secret-jobservice: {{ include (print $.Template.BasePath "/jobservice/jobservice-secrets.yaml") . | sha256sum }} + checksum/secret-core: {{ include (print $.Template.BasePath "/core/core-secret.yaml") . | sha256sum }} +{{- if and .Values.internalTLS.enabled (eq .Values.internalTLS.certSource "auto") }} + checksum/tls: {{ include (print $.Template.BasePath "/internal/auto-tls.yaml") . | sha256sum }} +{{- else if and .Values.internalTLS.enabled (eq .Values.internalTLS.certSource "manual") }} + checksum/tls: {{ include (print $.Template.BasePath "/registry/registry-tls.yaml") . | sha256sum }} +{{- end }} +{{- if .Values.registry.podAnnotations }} +{{ toYaml .Values.registry.podAnnotations | indent 8 }} +{{- end }} + spec: + securityContext: + runAsUser: 10000 + fsGroup: 10000 +{{- if .Values.registry.serviceAccountName }} + serviceAccountName: {{ .Values.registry.serviceAccountName }} +{{- end -}} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + automountServiceAccountToken: {{ .Values.registry.automountServiceAccountToken | default false }} + terminationGracePeriodSeconds: 120 + containers: + - name: registry + image: {{ .Values.registry.registry.image.repository }}:{{ .Values.registry.registry.image.tag }} + imagePullPolicy: {{ .Values.imagePullPolicy }} + livenessProbe: + httpGet: + path: / + scheme: {{ include "harbor.component.scheme" . | upper }} + port: {{ template "harbor.registry.containerPort" . }} + initialDelaySeconds: 300 + periodSeconds: 10 + readinessProbe: + httpGet: + path: / + scheme: {{ include "harbor.component.scheme" . | upper }} + port: {{ template "harbor.registry.containerPort" . }} + initialDelaySeconds: 1 + periodSeconds: 10 +{{- if .Values.registry.registry.resources }} + resources: +{{ toYaml .Values.registry.registry.resources | indent 10 }} +{{- end }} + args: ["serve", "/etc/registry/config.yml"] + envFrom: + - secretRef: + name: "{{ template "harbor.registry" . }}" + env: + {{- if has "registry" .Values.proxy.components }} + - name: HTTP_PROXY + value: "{{ .Values.proxy.httpProxy }}" + - name: HTTPS_PROXY + value: "{{ .Values.proxy.httpsProxy }}" + - name: NO_PROXY + value: "{{ template "harbor.noProxy" . }}" + {{- end }} + {{- if .Values.internalTLS.enabled }} + - name: INTERNAL_TLS_ENABLED + value: "true" + - name: INTERNAL_TLS_KEY_PATH + value: /etc/harbor/ssl/registry/tls.key + - name: INTERNAL_TLS_CERT_PATH + value: /etc/harbor/ssl/registry/tls.crt + - name: INTERNAL_TLS_TRUST_CA_PATH + value: /etc/harbor/ssl/registry/ca.crt + {{- end }} + ports: + - containerPort: {{ template "harbor.registry.containerPort" . }} + - containerPort: 5001 + volumeMounts: + - name: registry-data + mountPath: {{ .Values.persistence.imageChartStorage.filesystem.rootdirectory }} + subPath: {{ .Values.persistence.persistentVolumeClaim.registry.subPath }} + - name: registry-htpasswd + mountPath: /etc/registry/passwd + subPath: passwd + - name: registry-config + mountPath: /etc/registry/config.yml + subPath: config.yml + {{- if .Values.internalTLS.enabled }} + - name: registry-internal-certs + mountPath: /etc/harbor/ssl/registry + {{- end }} + {{- if and .Values.persistence.enabled (eq .Values.persistence.imageChartStorage.type "gcs") }} + - name: gcs-key + mountPath: /etc/registry/gcs-key.json + subPath: gcs-key.json + {{- end }} + {{- if .Values.persistence.imageChartStorage.caBundleSecretName }} + - name: storage-service-ca + mountPath: /harbor_cust_cert/custom-ca-bundle.crt + subPath: ca.crt + {{- end }} + {{- if .Values.registry.middleware.enabled }} + {{- if eq .Values.registry.middleware.type "cloudFront" }} + - name: cloudfront-key + mountPath: /etc/registry/pk.pem + subPath: pk.pem + {{- end }} + {{- end }} + {{- if .Values.caBundleSecretName }} +{{ include "harbor.caBundleVolumeMount" . | indent 8 }} + {{- end }} + - name: registryctl + image: {{ .Values.registry.controller.image.repository }}:{{ .Values.registry.controller.image.tag }} + imagePullPolicy: {{ .Values.imagePullPolicy }} + livenessProbe: + httpGet: + path: /api/health + scheme: {{ include "harbor.component.scheme" . | upper }} + port: {{ template "harbor.registryctl.containerPort" . }} + initialDelaySeconds: 300 + periodSeconds: 10 + readinessProbe: + httpGet: + path: /api/health + scheme: {{ include "harbor.component.scheme" . | upper }} + port: {{ template "harbor.registryctl.containerPort" . }} + initialDelaySeconds: 1 + periodSeconds: 10 +{{- if .Values.registry.controller.resources }} + resources: +{{ toYaml .Values.registry.controller.resources | indent 10 }} +{{- end }} + envFrom: + - configMapRef: + name: "{{ template "harbor.registryCtl" . }}" + - secretRef: + name: "{{ template "harbor.registry" . }}" + - secretRef: + name: "{{ template "harbor.registryCtl" . }}" + env: + - name: CORE_SECRET + valueFrom: + secretKeyRef: + name: {{ template "harbor.core" . }} + key: secret + - name: JOBSERVICE_SECRET + valueFrom: + secretKeyRef: + name: {{ template "harbor.jobservice" . }} + key: JOBSERVICE_SECRET + {{- if has "registry" .Values.proxy.components }} + - name: HTTP_PROXY + value: "{{ .Values.proxy.httpProxy }}" + - name: HTTPS_PROXY + value: "{{ .Values.proxy.httpsProxy }}" + - name: NO_PROXY + value: "{{ template "harbor.noProxy" . }}" + {{- end }} + {{- if .Values.internalTLS.enabled }} + - name: INTERNAL_TLS_ENABLED + value: "true" + - name: INTERNAL_TLS_KEY_PATH + value: /etc/harbor/ssl/registry/tls.key + - name: INTERNAL_TLS_CERT_PATH + value: /etc/harbor/ssl/registry/tls.crt + - name: INTERNAL_TLS_TRUST_CA_PATH + value: /etc/harbor/ssl/registry/ca.crt + {{- end }} + ports: + - containerPort: {{ template "harbor.registryctl.containerPort" . }} + volumeMounts: + - name: registry-data + mountPath: {{ .Values.persistence.imageChartStorage.filesystem.rootdirectory }} + subPath: {{ .Values.persistence.persistentVolumeClaim.registry.subPath }} + - name: registry-config + mountPath: /etc/registry/config.yml + subPath: config.yml + - name: registry-config + mountPath: /etc/registryctl/config.yml + subPath: ctl-config.yml + {{- if .Values.internalTLS.enabled }} + - name: registry-internal-certs + mountPath: /etc/harbor/ssl/registry + {{- end }} + {{- if .Values.persistence.imageChartStorage.caBundleSecretName }} + - name: storage-service-ca + mountPath: /harbor_cust_cert/custom-ca-bundle.crt + subPath: ca.crt + {{- end }} + {{- if and .Values.persistence.enabled (eq .Values.persistence.imageChartStorage.type "gcs") }} + - name: gcs-key + mountPath: /etc/registry/gcs-key.json + subPath: gcs-key.json + {{- end }} + {{- if .Values.caBundleSecretName }} +{{ include "harbor.caBundleVolumeMount" . | indent 8 }} + {{- end }} + volumes: + - name: registry-htpasswd + secret: + secretName: {{ template "harbor.registry" . }}-htpasswd + items: + - key: REGISTRY_HTPASSWD + path: passwd + - name: registry-config + configMap: + name: "{{ template "harbor.registry" . }}" + - name: registry-data + {{- if and .Values.persistence.enabled (eq .Values.persistence.imageChartStorage.type "filesystem") }} + persistentVolumeClaim: + claimName: {{ .Values.persistence.persistentVolumeClaim.registry.existingClaim | default (include "harbor.registry" .) }} + {{- else }} + emptyDir: {} + {{- end }} + {{- if .Values.internalTLS.enabled }} + - name: registry-internal-certs + secret: + secretName: {{ template "harbor.internalTLS.registry.secretName" . }} + {{- end }} + {{- if and .Values.persistence.enabled (eq .Values.persistence.imageChartStorage.type "gcs") }} + - name: gcs-key + secret: + secretName: {{ template "harbor.registry" . }} + items: + - key: GCS_KEY_DATA + path: gcs-key.json + {{- end }} + {{- if .Values.persistence.imageChartStorage.caBundleSecretName }} + - name: storage-service-ca + secret: + secretName: {{ .Values.persistence.imageChartStorage.caBundleSecretName }} + {{- end }} + {{- if .Values.registry.middleware.enabled }} + {{- if eq .Values.registry.middleware.type "cloudFront" }} + - name: cloudfront-key + secret: + secretName: {{ .Values.registry.middleware.cloudFront.privateKeySecret }} + items: + - key: CLOUDFRONT_KEY_DATA + path: pk.pem + {{- end }} + {{- end }} + {{- if .Values.caBundleSecretName }} +{{ include "harbor.caBundleVolume" . | indent 6 }} + {{- end }} + {{- with .Values.registry.nodeSelector }} + nodeSelector: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.registry.affinity }} + affinity: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.registry.tolerations }} + tolerations: +{{ toYaml . | indent 8 }} + {{- end }} + {{- if .Values.registry.priorityClassName }} + priorityClassName: {{ .Values.registry.priorityClassName }} + {{- end }} diff --git a/ResearchOps/harbor/templates/registry/registry-pvc.yaml b/ResearchOps/harbor/templates/registry/registry-pvc.yaml new file mode 100644 index 0000000..2112e22 --- /dev/null +++ b/ResearchOps/harbor/templates/registry/registry-pvc.yaml @@ -0,0 +1,32 @@ +{{- if .Values.persistence.enabled }} +{{- $registry := .Values.persistence.persistentVolumeClaim.registry -}} +{{- if and (not $registry.existingClaim) (eq .Values.persistence.imageChartStorage.type "filesystem") }} +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: {{ template "harbor.registry" . }} + annotations: + {{- range $key, $value := $registry.annotations }} + {{ $key }}: {{ $value | quote }} + {{- end }} + {{- if eq .Values.persistence.resourcePolicy "keep" }} + helm.sh/resource-policy: keep + {{- end }} + labels: +{{ include "harbor.labels" . | indent 4 }} + component: registry +spec: + accessModes: + - {{ $registry.accessMode }} + resources: + requests: + storage: {{ $registry.size }} + {{- if $registry.storageClass }} + {{- if eq "-" $registry.storageClass }} + storageClassName: "" + {{- else }} + storageClassName: {{ $registry.storageClass }} + {{- end }} + {{- end }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/ResearchOps/harbor/templates/registry/registry-secret.yaml b/ResearchOps/harbor/templates/registry/registry-secret.yaml new file mode 100644 index 0000000..de8dada --- /dev/null +++ b/ResearchOps/harbor/templates/registry/registry-secret.yaml @@ -0,0 +1,48 @@ +apiVersion: v1 +kind: Secret +metadata: + name: "{{ template "harbor.registry" . }}" + labels: +{{ include "harbor.labels" . | indent 4 }} +type: Opaque +data: + REGISTRY_HTTP_SECRET: {{ .Values.registry.secret | default (randAlphaNum 16) | b64enc | quote }} + REGISTRY_REDIS_PASSWORD: {{ (include "harbor.redis.password" .) | b64enc | quote }} + {{- $storage := .Values.persistence.imageChartStorage }} + {{- $type := $storage.type }} + {{- if eq $type "azure" }} + REGISTRY_STORAGE_AZURE_ACCOUNTKEY: {{ $storage.azure.accountkey | b64enc | quote }} + {{- else if eq $type "gcs" }} + GCS_KEY_DATA: {{ $storage.gcs.encodedkey | quote }} + {{- else if eq $type "s3" }} + {{- if $storage.s3.accesskey }} + REGISTRY_STORAGE_S3_ACCESSKEY: {{ $storage.s3.accesskey | b64enc | quote }} + {{- end }} + {{- if $storage.s3.secretkey }} + REGISTRY_STORAGE_S3_SECRETKEY: {{ $storage.s3.secretkey | b64enc | quote }} + {{- end }} + {{- else if eq $type "swift" }} + REGISTRY_STORAGE_SWIFT_PASSWORD: {{ $storage.swift.password | b64enc | quote }} + {{- if $storage.swift.secretkey }} + REGISTRY_STORAGE_SWIFT_SECRETKEY: {{ $storage.swift.secretkey | b64enc | quote }} + {{- end }} + {{- if $storage.swift.accesskey }} + REGISTRY_STORAGE_SWIFT_ACCESSKEY: {{ $storage.swift.accesskey | b64enc | quote }} + {{- end }} + {{- else if eq $type "oss" }} + REGISTRY_STORAGE_OSS_ACCESSKEYSECRET: {{ $storage.oss.accesskeysecret | b64enc | quote }} + {{- end }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: "{{ template "harbor.registry" . }}-htpasswd" + labels: +{{ include "harbor.labels" . | indent 4 }} +type: Opaque +data: + {{- if .Values.registry.credentials.htpasswdString }} + REGISTRY_HTPASSWD: {{ .Values.registry.credentials.htpasswdString | b64enc | quote }} + {{- else }} + REGISTRY_HTPASSWD: {{ htpasswd .Values.registry.credentials.username .Values.registry.credentials.password | b64enc | quote }} + {{- end }} diff --git a/ResearchOps/harbor/templates/registry/registry-svc.yaml b/ResearchOps/harbor/templates/registry/registry-svc.yaml new file mode 100644 index 0000000..749690e --- /dev/null +++ b/ResearchOps/harbor/templates/registry/registry-svc.yaml @@ -0,0 +1,20 @@ +apiVersion: v1 +kind: Service +metadata: + name: "{{ template "harbor.registry" . }}" + labels: +{{ include "harbor.labels" . | indent 4 }} +spec: + ports: + - name: {{ ternary "https-registry" "http-registry" .Values.internalTLS.enabled }} + port: {{ template "harbor.registry.servicePort" . }} + + - name: {{ ternary "https-controller" "http-controller" .Values.internalTLS.enabled }} + port: {{ template "harbor.registryctl.servicePort" . }} +{{- if .Values.metrics.enabled}} + - name: {{ template "harbor.metricsPortName" . }} + port: {{ .Values.metrics.registry.port }} +{{- end }} + selector: +{{ include "harbor.matchLabels" . | indent 4 }} + component: registry \ No newline at end of file diff --git a/ResearchOps/harbor/templates/registry/registry-tls.yaml b/ResearchOps/harbor/templates/registry/registry-tls.yaml new file mode 100644 index 0000000..9d1862c --- /dev/null +++ b/ResearchOps/harbor/templates/registry/registry-tls.yaml @@ -0,0 +1,15 @@ +{{- if and .Values.internalTLS.enabled }} +{{- if eq .Values.internalTLS.certSource "manual" }} +apiVersion: v1 +kind: Secret +metadata: + name: "{{ template "harbor.internalTLS.registry.secretName" . }}" + labels: +{{ include "harbor.labels" . | indent 4 }} +type: kubernetes.io/tls +data: + ca.crt: {{ (required "The \"internalTLS.trustCa\" is required!" .Values.internalTLS.trustCa) | b64enc | quote }} + tls.crt: {{ (required "The \"internalTLS.registry.crt\" is required!" .Values.internalTLS.registry.crt) | b64enc | quote }} + tls.key: {{ (required "The \"internalTLS.registry.key\" is required!" .Values.internalTLS.registry.key) | b64enc | quote }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/ResearchOps/harbor/templates/registry/registryctl-cm.yaml b/ResearchOps/harbor/templates/registry/registryctl-cm.yaml new file mode 100644 index 0000000..87aa5ff --- /dev/null +++ b/ResearchOps/harbor/templates/registry/registryctl-cm.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: "{{ template "harbor.registryCtl" . }}" + labels: +{{ include "harbor.labels" . | indent 4 }} +data: + {{- template "harbor.traceEnvsForRegistryCtl" . }} diff --git a/ResearchOps/harbor/templates/registry/registryctl-secret.yaml b/ResearchOps/harbor/templates/registry/registryctl-secret.yaml new file mode 100644 index 0000000..7009770 --- /dev/null +++ b/ResearchOps/harbor/templates/registry/registryctl-secret.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Secret +metadata: + name: "{{ template "harbor.registryCtl" . }}" + labels: +{{ include "harbor.labels" . | indent 4 }} +type: Opaque +data: + {{- template "harbor.traceJaegerPassword" . }} \ No newline at end of file diff --git a/ResearchOps/harbor/templates/trivy/trivy-secret.yaml b/ResearchOps/harbor/templates/trivy/trivy-secret.yaml new file mode 100644 index 0000000..84652c7 --- /dev/null +++ b/ResearchOps/harbor/templates/trivy/trivy-secret.yaml @@ -0,0 +1,12 @@ +{{- if .Values.trivy.enabled }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "harbor.trivy" . }} + labels: +{{ include "harbor.labels" . | indent 4 }} +type: Opaque +data: + redisURL: {{ include "harbor.redis.urlForTrivy" . | b64enc }} + gitHubToken: {{ .Values.trivy.gitHubToken | default "" | b64enc | quote }} +{{- end }} diff --git a/ResearchOps/harbor/templates/trivy/trivy-sts.yaml b/ResearchOps/harbor/templates/trivy/trivy-sts.yaml new file mode 100644 index 0000000..78d915e --- /dev/null +++ b/ResearchOps/harbor/templates/trivy/trivy-sts.yaml @@ -0,0 +1,204 @@ +{{- if .Values.trivy.enabled }} +{{- $trivy := .Values.persistence.persistentVolumeClaim.trivy }} +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ template "harbor.trivy" . }} + labels: +{{ include "harbor.labels" . | indent 4 }} + component: trivy +spec: + replicas: {{ .Values.trivy.replicas }} + serviceName: {{ template "harbor.trivy" . }} + selector: + matchLabels: +{{ include "harbor.matchLabels" . | indent 6 }} + component: trivy + template: + metadata: + labels: +{{ include "harbor.labels" . | indent 8 }} + component: trivy + annotations: + checksum/secret: {{ include (print $.Template.BasePath "/trivy/trivy-secret.yaml") . | sha256sum }} +{{- if and .Values.internalTLS.enabled (eq .Values.internalTLS.certSource "auto") }} + checksum/tls: {{ include (print $.Template.BasePath "/internal/auto-tls.yaml") . | sha256sum }} +{{- else if and .Values.internalTLS.enabled (eq .Values.internalTLS.certSource "manual") }} + checksum/tls: {{ include (print $.Template.BasePath "/trivy/trivy-tls.yaml") . | sha256sum }} +{{- end }} +{{- if .Values.trivy.podAnnotations }} +{{ toYaml .Values.trivy.podAnnotations | indent 8 }} +{{- end }} + spec: +{{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} +{{- end }} +{{- if .Values.trivy.serviceAccountName }} + serviceAccountName: {{ .Values.trivy.serviceAccountName }} +{{- end }} + securityContext: + runAsUser: 10000 + fsGroup: 10000 + automountServiceAccountToken: {{ .Values.trivy.automountServiceAccountToken | default false }} + containers: + - name: trivy + image: {{ .Values.trivy.image.repository }}:{{ .Values.trivy.image.tag }} + imagePullPolicy: {{ .Values.imagePullPolicy }} + securityContext: + privileged: false + allowPrivilegeEscalation: false + env: + {{- if has "trivy" .Values.proxy.components }} + - name: HTTP_PROXY + value: "{{ .Values.proxy.httpProxy }}" + - name: HTTPS_PROXY + value: "{{ .Values.proxy.httpsProxy }}" + - name: NO_PROXY + value: "{{ template "harbor.noProxy" . }}" + {{- end }} + - name: "SCANNER_LOG_LEVEL" + value: {{ .Values.logLevel | quote }} + - name: "SCANNER_TRIVY_CACHE_DIR" + value: "/home/scanner/.cache/trivy" + - name: "SCANNER_TRIVY_REPORTS_DIR" + value: "/home/scanner/.cache/reports" + - name: "SCANNER_TRIVY_DEBUG_MODE" + value: {{ .Values.trivy.debugMode | quote }} + - name: "SCANNER_TRIVY_VULN_TYPE" + value: {{ .Values.trivy.vulnType | quote }} + - name: "SCANNER_TRIVY_TIMEOUT" + value: {{ .Values.trivy.timeout | quote }} + - name: "SCANNER_TRIVY_GITHUB_TOKEN" + valueFrom: + secretKeyRef: + name: {{ template "harbor.trivy" . }} + key: gitHubToken + - name: "SCANNER_TRIVY_SEVERITY" + value: {{ .Values.trivy.severity | quote }} + - name: "SCANNER_TRIVY_IGNORE_UNFIXED" + value: {{ .Values.trivy.ignoreUnfixed | default false | quote }} + - name: "SCANNER_TRIVY_SKIP_UPDATE" + value: {{ .Values.trivy.skipUpdate | default false | quote }} + - name: "SCANNER_TRIVY_OFFLINE_SCAN" + value: {{ .Values.trivy.offlineScan | default false | quote }} + - name: "SCANNER_TRIVY_INSECURE" + value: {{ .Values.trivy.insecure | default false | quote }} + - name: SCANNER_API_SERVER_ADDR + value: ":{{ template "harbor.trivy.containerPort" . }}" + {{- if .Values.internalTLS.enabled }} + - name: INTERNAL_TLS_ENABLED + value: "true" + - name: SCANNER_API_SERVER_TLS_KEY + value: /etc/harbor/ssl/trivy/tls.key + - name: SCANNER_API_SERVER_TLS_CERTIFICATE + value: /etc/harbor/ssl/trivy/tls.crt + {{- end }} + - name: "SCANNER_REDIS_URL" + valueFrom: + secretKeyRef: + name: {{ template "harbor.trivy" . }} + key: redisURL + - name: "SCANNER_STORE_REDIS_URL" + valueFrom: + secretKeyRef: + name: {{ template "harbor.trivy" . }} + key: redisURL + - name: "SCANNER_JOB_QUEUE_REDIS_URL" + valueFrom: + secretKeyRef: + name: {{ template "harbor.trivy" . }} + key: redisURL + ports: + - name: api-server + containerPort: {{ template "harbor.trivy.containerPort" . }} + volumeMounts: + - name: data + mountPath: /home/scanner/.cache + subPath: {{ .Values.persistence.persistentVolumeClaim.trivy.subPath }} + readOnly: false + {{- if .Values.internalTLS.enabled }} + - name: trivy-internal-certs + mountPath: /etc/harbor/ssl/trivy + {{- end }} + {{- if .Values.caBundleSecretName }} +{{ include "harbor.caBundleVolumeMount" . | indent 10 }} + {{- end }} + livenessProbe: + httpGet: + scheme: {{ include "harbor.component.scheme" . | upper }} + path: /probe/healthy + port: api-server + initialDelaySeconds: 5 + periodSeconds: 10 + successThreshold: 1 + failureThreshold: 10 + readinessProbe: + httpGet: + scheme: {{ include "harbor.component.scheme" . | upper }} + path: /probe/ready + port: api-server + initialDelaySeconds: 5 + periodSeconds: 10 + successThreshold: 1 + failureThreshold: 3 + resources: +{{ toYaml .Values.trivy.resources | indent 12 }} + {{- if or (or .Values.internalTLS.enabled .Values.caBundleSecretName) (or (not .Values.persistence.enabled) $trivy.existingClaim) }} + volumes: + {{- if .Values.internalTLS.enabled }} + - name: trivy-internal-certs + secret: + secretName: {{ template "harbor.internalTLS.trivy.secretName" . }} + {{- end }} + {{- if .Values.caBundleSecretName }} +{{ include "harbor.caBundleVolume" . | indent 6 }} + {{- end }} + {{- if not .Values.persistence.enabled }} + - name: "data" + emptyDir: {} + {{- else if $trivy.existingClaim }} + - name: "data" + persistentVolumeClaim: + claimName: {{ $trivy.existingClaim }} + {{- end }} + {{- end }} + {{- with .Values.trivy.nodeSelector }} + nodeSelector: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.trivy.affinity }} + affinity: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.trivy.tolerations }} + tolerations: +{{ toYaml . | indent 8 }} + {{- end }} + {{- if .Values.trivy.priorityClassName }} + priorityClassName: {{ .Values.trivy.priorityClassName }} + {{- end }} +{{- if and .Values.persistence.enabled (not $trivy.existingClaim) }} + volumeClaimTemplates: + - metadata: + name: data + labels: +{{ include "harbor.labels" . | indent 8 }} + annotations: + {{- range $key, $value := $trivy.annotations }} + {{ $key }}: {{ $value | quote }} + {{- end }} + spec: + accessModes: [{{ $trivy.accessMode | quote }}] + {{- if $trivy.storageClass }} + {{- if (eq "-" $trivy.storageClass) }} + storageClassName: "" + {{- else }} + storageClassName: "{{ $trivy.storageClass }}" + {{- end }} + {{- end }} + resources: + requests: + storage: {{ $trivy.size | quote }} +{{- end }} +{{- end }} diff --git a/ResearchOps/harbor/templates/trivy/trivy-svc.yaml b/ResearchOps/harbor/templates/trivy/trivy-svc.yaml new file mode 100644 index 0000000..24daf09 --- /dev/null +++ b/ResearchOps/harbor/templates/trivy/trivy-svc.yaml @@ -0,0 +1,16 @@ +{{ if .Values.trivy.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: "{{ template "harbor.trivy" . }}" + labels: +{{ include "harbor.labels" . | indent 4 }} +spec: + ports: + - name: {{ ternary "https-trivy" "http-trivy" .Values.internalTLS.enabled }} + protocol: TCP + port: {{ template "harbor.trivy.servicePort" . }} + selector: +{{ include "harbor.matchLabels" . | indent 4 }} + component: trivy +{{ end }} diff --git a/ResearchOps/harbor/templates/trivy/trivy-tls.yaml b/ResearchOps/harbor/templates/trivy/trivy-tls.yaml new file mode 100644 index 0000000..a9c8330 --- /dev/null +++ b/ResearchOps/harbor/templates/trivy/trivy-tls.yaml @@ -0,0 +1,15 @@ +{{- if and .Values.trivy.enabled .Values.internalTLS.enabled }} +{{- if eq .Values.internalTLS.certSource "manual" }} +apiVersion: v1 +kind: Secret +metadata: + name: "{{ template "harbor.internalTLS.trivy.secretName" . }}" + labels: +{{ include "harbor.labels" . | indent 4 }} +type: kubernetes.io/tls +data: + ca.crt: {{ (required "The \"internalTLS.trustCa\" is required!" .Values.internalTLS.trustCa) | b64enc | quote }} + tls.crt: {{ (required "The \"internalTLS.trivy.crt\" is required!" .Values.internalTLS.trivy.crt) | b64enc | quote }} + tls.key: {{ (required "The \"internalTLS.trivy.key\" is required!" .Values.internalTLS.trivy.key) | b64enc | quote }} +{{- end }} +{{- end }} diff --git a/ResearchOps/harbor/values.yaml b/ResearchOps/harbor/values.yaml new file mode 100644 index 0000000..7336c8a --- /dev/null +++ b/ResearchOps/harbor/values.yaml @@ -0,0 +1,923 @@ +expose: + # Set how to expose the service. Set the type as "ingress", "clusterIP", "nodePort" or "loadBalancer" + # and fill the information in the corresponding section + type: ingress + tls: + # Enable TLS or not. + # Delete the "ssl-redirect" annotations in "expose.ingress.annotations" when TLS is disabled and "expose.type" is "ingress" + # Note: if the "expose.type" is "ingress" and TLS is disabled, + # the port must be included in the command when pulling/pushing images. + # Refer to https://github.com/goharbor/harbor/issues/5291 for details. + enabled: true + # The source of the tls certificate. Set as "auto", "secret" + # or "none" and fill the information in the corresponding section + # 1) auto: generate the tls certificate automatically + # 2) secret: read the tls certificate from the specified secret. + # The tls certificate can be generated manually or by cert manager + # 3) none: configure no tls certificate for the ingress. If the default + # tls certificate is configured in the ingress controller, choose this option + certSource: secret + auto: + # The common name used to generate the certificate, it's necessary + # when the type isn't "ingress" + commonName: "" + secret: + # The name of secret which contains keys named: + # "tls.crt" - the certificate + # "tls.key" - the private key + secretName: "harbor-abu-pub-tls" + # The name of secret which contains keys named: + # "tls.crt" - the certificate + # "tls.key" - the private key + # Only needed when the "expose.type" is "ingress". + notarySecretName: "harbor-abu-pub-tls" + ingress: + hosts: + core: harbor.abu.pub + notary: notary.abu.pub + # set to the type of ingress controller if it has specific requirements. + # leave as `default` for most ingress controllers. + # set to `gce` if using the GCE ingress controller + # set to `ncp` if using the NCP (NSX-T Container Plugin) ingress controller + controller: default + ## Allow .Capabilities.KubeVersion.Version to be overridden while creating ingress + kubeVersionOverride: "" + className: "" + annotations: + # note different ingress controllers may require a different ssl-redirect annotation + # for Envoy, use ingress.kubernetes.io/force-ssl-redirect: "true" and remove the nginx lines below + ingress.kubernetes.io/ssl-redirect: "true" + ingress.kubernetes.io/proxy-body-size: "0" + nginx.ingress.kubernetes.io/ssl-redirect: "true" + nginx.ingress.kubernetes.io/proxy-body-size: "0" + notary: + # notary ingress-specific annotations + annotations: {} + # notary ingress-specific labels + labels: {} + harbor: + # harbor ingress-specific annotations + annotations: {} + # harbor ingress-specific labels + labels: {} + clusterIP: + # The name of ClusterIP service + name: harbor + # Annotations on the ClusterIP service + annotations: {} + ports: + # The service port Harbor listens on when serving HTTP + httpPort: 80 + # The service port Harbor listens on when serving HTTPS + httpsPort: 443 + # The service port Notary listens on. Only needed when notary.enabled + # is set to true + notaryPort: 4443 + nodePort: + # The name of NodePort service + name: harbor + ports: + http: + # The service port Harbor listens on when serving HTTP + port: 80 + # The node port Harbor listens on when serving HTTP + nodePort: 30002 + https: + # The service port Harbor listens on when serving HTTPS + port: 443 + # The node port Harbor listens on when serving HTTPS + nodePort: 30003 + # Only needed when notary.enabled is set to true + notary: + # The service port Notary listens on + port: 4443 + # The node port Notary listens on + nodePort: 30004 + loadBalancer: + # The name of LoadBalancer service + name: harbor + # Set the IP if the LoadBalancer supports assigning IP + IP: "" + ports: + # The service port Harbor listens on when serving HTTP + httpPort: 80 + # The service port Harbor listens on when serving HTTPS + httpsPort: 443 + # The service port Notary listens on. Only needed when notary.enabled + # is set to true + notaryPort: 4443 + annotations: {} + sourceRanges: [] + +# The external URL for Harbor core service. It is used to +# 1) populate the docker/helm commands showed on portal +# 2) populate the token service URL returned to docker/notary client +# +# Format: protocol://domain[:port]. Usually: +# 1) if "expose.type" is "ingress", the "domain" should be +# the value of "expose.ingress.hosts.core" +# 2) if "expose.type" is "clusterIP", the "domain" should be +# the value of "expose.clusterIP.name" +# 3) if "expose.type" is "nodePort", the "domain" should be +# the IP address of k8s node +# +# If Harbor is deployed behind the proxy, set it as the URL of proxy +externalURL: https://harbor.abu.pub + +# The internal TLS used for harbor components secure communicating. In order to enable https +# in each components tls cert files need to provided in advance. +internalTLS: + # If internal TLS enabled + enabled: false + # There are three ways to provide tls + # 1) "auto" will generate cert automatically + # 2) "manual" need provide cert file manually in following value + # 3) "secret" internal certificates from secret + certSource: "auto" + # The content of trust ca, only available when `certSource` is "manual" + trustCa: "" + # core related cert configuration + core: + # secret name for core's tls certs + secretName: "" + # Content of core's TLS cert file, only available when `certSource` is "manual" + crt: "" + # Content of core's TLS key file, only available when `certSource` is "manual" + key: "" + # jobservice related cert configuration + jobservice: + # secret name for jobservice's tls certs + secretName: "" + # Content of jobservice's TLS key file, only available when `certSource` is "manual" + crt: "" + # Content of jobservice's TLS key file, only available when `certSource` is "manual" + key: "" + # registry related cert configuration + registry: + # secret name for registry's tls certs + secretName: "" + # Content of registry's TLS key file, only available when `certSource` is "manual" + crt: "" + # Content of registry's TLS key file, only available when `certSource` is "manual" + key: "" + # portal related cert configuration + portal: + # secret name for portal's tls certs + secretName: "" + # Content of portal's TLS key file, only available when `certSource` is "manual" + crt: "" + # Content of portal's TLS key file, only available when `certSource` is "manual" + key: "" + # chartmuseum related cert configuration + chartmuseum: + # secret name for chartmuseum's tls certs + secretName: "" + # Content of chartmuseum's TLS key file, only available when `certSource` is "manual" + crt: "" + # Content of chartmuseum's TLS key file, only available when `certSource` is "manual" + key: "" + # trivy related cert configuration + trivy: + # secret name for trivy's tls certs + secretName: "" + # Content of trivy's TLS key file, only available when `certSource` is "manual" + crt: "" + # Content of trivy's TLS key file, only available when `certSource` is "manual" + key: "" + +ipFamily: + # ipv6Enabled set to true if ipv6 is enabled in cluster, currently it affected the nginx related component + ipv6: + enabled: true + # ipv4Enabled set to true if ipv4 is enabled in cluster, currently it affected the nginx related component + ipv4: + enabled: true + +# The persistence is enabled by default and a default StorageClass +# is needed in the k8s cluster to provision volumes dynamically. +# Specify another StorageClass in the "storageClass" or set "existingClaim" +# if you already have existing persistent volumes to use +# +# For storing images and charts, you can also use "azure", "gcs", "s3", +# "swift" or "oss". Set it in the "imageChartStorage" section +persistence: + enabled: true + # Setting it to "keep" to avoid removing PVCs during a helm delete + # operation. Leaving it empty will delete PVCs after the chart deleted + # (this does not apply for PVCs that are created for internal database + # and redis components, i.e. they are never deleted automatically) + resourcePolicy: "keep" + persistentVolumeClaim: + registry: + # Use the existing PVC which must be created manually before bound, + # and specify the "subPath" if the PVC is shared with other components + existingClaim: "" + # Specify the "storageClass" used to provision the volume. Or the default + # StorageClass will be used (the default). + # Set it to "-" to disable dynamic provisioning + storageClass: "" + subPath: "" + accessMode: ReadWriteOnce + size: 20Gi + annotations: {} + chartmuseum: + existingClaim: "" + storageClass: "" + subPath: "" + accessMode: ReadWriteOnce + size: 5Gi + annotations: {} + jobservice: + existingClaim: "" + storageClass: "" + subPath: "" + accessMode: ReadWriteOnce + size: 1Gi + annotations: {} + # If external database is used, the following settings for database will + # be ignored + database: + existingClaim: "" + storageClass: "" + subPath: "" + accessMode: ReadWriteOnce + size: 1Gi + annotations: {} + # If external Redis is used, the following settings for Redis will + # be ignored + redis: + existingClaim: "" + storageClass: "" + subPath: "" + accessMode: ReadWriteOnce + size: 1Gi + annotations: {} + trivy: + existingClaim: "" + storageClass: "" + subPath: "" + accessMode: ReadWriteOnce + size: 5Gi + annotations: {} + # Define which storage backend is used for registry and chartmuseum to store + # images and charts. Refer to + # https://github.com/docker/distribution/blob/master/docs/configuration.md#storage + # for the detail. + imageChartStorage: + # Specify whether to disable `redirect` for images and chart storage, for + # backends which not supported it (such as using minio for `s3` storage type), please disable + # it. To disable redirects, simply set `disableredirect` to `true` instead. + # Refer to + # https://github.com/docker/distribution/blob/master/docs/configuration.md#redirect + # for the detail. + disableredirect: false + # Specify the "caBundleSecretName" if the storage service uses a self-signed certificate. + # The secret must contain keys named "ca.crt" which will be injected into the trust store + # of registry's and chartmuseum's containers. + # caBundleSecretName: + + # Specify the type of storage: "filesystem", "azure", "gcs", "s3", "swift", + # "oss" and fill the information needed in the corresponding section. The type + # must be "filesystem" if you want to use persistent volumes for registry + # and chartmuseum + type: filesystem + filesystem: + rootdirectory: /storage + #maxthreads: 100 + azure: + accountname: accountname + accountkey: base64encodedaccountkey + container: containername + #realm: core.windows.net + gcs: + bucket: bucketname + # The base64 encoded json file which contains the key + encodedkey: base64-encoded-json-key-file + #rootdirectory: /gcs/object/name/prefix + #chunksize: "5242880" + s3: + region: us-west-1 + bucket: bucketname + #accesskey: awsaccesskey + #secretkey: awssecretkey + #regionendpoint: http://myobjects.local + #encrypt: false + #keyid: mykeyid + #secure: true + #skipverify: false + #v4auth: true + #chunksize: "5242880" + #rootdirectory: /s3/object/name/prefix + #storageclass: STANDARD + #multipartcopychunksize: "33554432" + #multipartcopymaxconcurrency: 100 + #multipartcopythresholdsize: "33554432" + swift: + authurl: https://storage.myprovider.com/v3/auth + username: username + password: password + container: containername + #region: fr + #tenant: tenantname + #tenantid: tenantid + #domain: domainname + #domainid: domainid + #trustid: trustid + #insecureskipverify: false + #chunksize: 5M + #prefix: + #secretkey: secretkey + #accesskey: accesskey + #authversion: 3 + #endpointtype: public + #tempurlcontainerkey: false + #tempurlmethods: + oss: + accesskeyid: accesskeyid + accesskeysecret: accesskeysecret + region: regionname + bucket: bucketname + #endpoint: endpoint + #internal: false + #encrypt: false + #secure: true + #chunksize: 10M + #rootdirectory: rootdirectory + +imagePullPolicy: IfNotPresent + +# Use this set to assign a list of default pullSecrets +imagePullSecrets: +# - name: docker-registry-secret +# - name: internal-registry-secret + +# The update strategy for deployments with persistent volumes(jobservice, registry +# and chartmuseum): "RollingUpdate" or "Recreate" +# Set it as "Recreate" when "RWM" for volumes isn't supported +updateStrategy: + type: RollingUpdate + +# debug, info, warning, error or fatal +logLevel: info + +# The initial password of Harbor admin. Change it from portal after launching Harbor +harborAdminPassword: "OpenSource@2022" + +# The name of the secret which contains key named "ca.crt". Setting this enables the +# download link on portal to download the CA certificate when the certificate isn't +# generated automatically +caSecretName: "" + +# The secret key used for encryption. Must be a string of 16 chars. +secretKey: "not-a-secure-key" + +# The proxy settings for updating trivy vulnerabilities from the Internet and replicating +# artifacts from/to the registries that cannot be reached directly +proxy: + httpProxy: + httpsProxy: + noProxy: 127.0.0.1,localhost,.local,.internal + components: + - core + - jobservice + - trivy + +# Run the migration job via helm hook +enableMigrateHelmHook: false + +# The custom ca bundle secret, the secret must contain key named "ca.crt" +# which will be injected into the trust store for chartmuseum, core, jobservice, registry, trivy components +# caBundleSecretName: "" + +## UAA Authentication Options +# If you're using UAA for authentication behind a self-signed +# certificate you will need to provide the CA Cert. +# Set uaaSecretName below to provide a pre-created secret that +# contains a base64 encoded CA Certificate named `ca.crt`. +# uaaSecretName: + +# If service exposed via "ingress", the Nginx will not be used +nginx: + image: + repository: goharbor/nginx-photon + tag: v2.5.3 + # set the service account to be used, default if left empty + serviceAccountName: "" + # mount the service account token + automountServiceAccountToken: false + replicas: 1 + revisionHistoryLimit: 10 + # resources: + # requests: + # memory: 256Mi + # cpu: 100m + nodeSelector: {} + tolerations: [] + affinity: {} + ## Additional deployment annotations + podAnnotations: {} + ## The priority class to run the pod as + priorityClassName: + +portal: + image: + repository: goharbor/harbor-portal + tag: v2.5.3 + # set the service account to be used, default if left empty + serviceAccountName: "" + # mount the service account token + automountServiceAccountToken: false + replicas: 1 + revisionHistoryLimit: 10 + # resources: + # requests: + # memory: 256Mi + # cpu: 100m + nodeSelector: {} + tolerations: [] + affinity: {} + ## Additional deployment annotations + podAnnotations: {} + ## The priority class to run the pod as + priorityClassName: + +core: + image: + repository: goharbor/harbor-core + tag: v2.5.3 + # set the service account to be used, default if left empty + serviceAccountName: "" + # mount the service account token + automountServiceAccountToken: false + replicas: 1 + revisionHistoryLimit: 10 + ## Startup probe values + startupProbe: + enabled: true + initialDelaySeconds: 10 + # resources: + # requests: + # memory: 256Mi + # cpu: 100m + nodeSelector: {} + tolerations: [] + affinity: {} + ## Additional deployment annotations + podAnnotations: {} + # Secret is used when core server communicates with other components. + # If a secret key is not specified, Helm will generate one. + # Must be a string of 16 chars. + secret: "" + # Fill the name of a kubernetes secret if you want to use your own + # TLS certificate and private key for token encryption/decryption. + # The secret must contain keys named: + # "tls.crt" - the certificate + # "tls.key" - the private key + # The default key pair will be used if it isn't set + secretName: "" + # The XSRF key. Will be generated automatically if it isn't specified + xsrfKey: "" + ## The priority class to run the pod as + priorityClassName: + # The time duration for async update artifact pull_time and repository + # pull_count, the unit is second. Will be 10 seconds if it isn't set. + # eg. artifactPullAsyncFlushDuration: 10 + artifactPullAsyncFlushDuration: + +jobservice: + image: + repository: goharbor/harbor-jobservice + tag: v2.5.3 + replicas: 1 + revisionHistoryLimit: 10 + # set the service account to be used, default if left empty + serviceAccountName: "" + # mount the service account token + automountServiceAccountToken: false + maxJobWorkers: 10 + # The logger for jobs: "file", "database" or "stdout" + jobLoggers: + - file + # - database + # - stdout + # The jobLogger sweeper duration (ignored if `jobLogger` is `stdout`) + loggerSweeperDuration: 14 #days + + # resources: + # requests: + # memory: 256Mi + # cpu: 100m + nodeSelector: {} + tolerations: [] + affinity: {} + ## Additional deployment annotations + podAnnotations: {} + # Secret is used when job service communicates with other components. + # If a secret key is not specified, Helm will generate one. + # Must be a string of 16 chars. + secret: "" + ## The priority class to run the pod as + priorityClassName: + +registry: + # set the service account to be used, default if left empty + serviceAccountName: "" + # mount the service account token + automountServiceAccountToken: false + registry: + image: + repository: goharbor/registry-photon + tag: v2.5.3 + # resources: + # requests: + # memory: 256Mi + # cpu: 100m + controller: + image: + repository: goharbor/harbor-registryctl + tag: v2.5.3 + + # resources: + # requests: + # memory: 256Mi + # cpu: 100m + replicas: 1 + revisionHistoryLimit: 10 + nodeSelector: {} + tolerations: [] + affinity: {} + ## Additional deployment annotations + podAnnotations: {} + ## The priority class to run the pod as + priorityClassName: + # Secret is used to secure the upload state from client + # and registry storage backend. + # See: https://github.com/docker/distribution/blob/master/docs/configuration.md#http + # If a secret key is not specified, Helm will generate one. + # Must be a string of 16 chars. + secret: "" + # If true, the registry returns relative URLs in Location headers. The client is responsible for resolving the correct URL. + relativeurls: false + credentials: + username: "harbor_registry_user" + password: "harbor_registry_password" + # Login and password in htpasswd string format. Excludes `registry.credentials.username` and `registry.credentials.password`. May come in handy when integrating with tools like argocd or flux. This allows the same line to be generated each time the template is rendered, instead of the `htpasswd` function from helm, which generates different lines each time because of the salt. + # htpasswdString: $apr1$XLefHzeG$Xl4.s00sMSCCcMyJljSZb0 # example string + middleware: + enabled: false + type: cloudFront + cloudFront: + baseurl: example.cloudfront.net + keypairid: KEYPAIRID + duration: 3000s + ipfilteredby: none + # The secret key that should be present is CLOUDFRONT_KEY_DATA, which should be the encoded private key + # that allows access to CloudFront + privateKeySecret: "my-secret" + # enable purge _upload directories + upload_purging: + enabled: true + # remove files in _upload directories which exist for a period of time, default is one week. + age: 168h + # the interval of the purge operations + interval: 24h + dryrun: false + +chartmuseum: + enabled: true + # set the service account to be used, default if left empty + serviceAccountName: "" + # mount the service account token + automountServiceAccountToken: false + # Harbor defaults ChartMuseum to returning relative urls, if you want using absolute url you should enable it by change the following value to 'true' + absoluteUrl: false + image: + repository: goharbor/chartmuseum-photon + tag: v2.5.3 + replicas: 1 + revisionHistoryLimit: 10 + # resources: + # requests: + # memory: 256Mi + # cpu: 100m + nodeSelector: {} + tolerations: [] + affinity: {} + ## Additional deployment annotations + podAnnotations: {} + ## The priority class to run the pod as + priorityClassName: + ## limit the number of parallel indexers + indexLimit: 0 + +trivy: + # enabled the flag to enable Trivy scanner + enabled: true + image: + # repository the repository for Trivy adapter image + repository: goharbor/trivy-adapter-photon + # tag the tag for Trivy adapter image + tag: v2.5.3 + # set the service account to be used, default if left empty + serviceAccountName: "" + # mount the service account token + automountServiceAccountToken: false + # replicas the number of Pod replicas + replicas: 1 + # debugMode the flag to enable Trivy debug mode with more verbose scanning log + debugMode: false + # vulnType a comma-separated list of vulnerability types. Possible values are `os` and `library`. + vulnType: "os,library" + # severity a comma-separated list of severities to be checked + severity: "UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL" + # ignoreUnfixed the flag to display only fixed vulnerabilities + ignoreUnfixed: false + # insecure the flag to skip verifying registry certificate + insecure: false + # gitHubToken the GitHub access token to download Trivy DB + # + # Trivy DB contains vulnerability information from NVD, Red Hat, and many other upstream vulnerability databases. + # It is downloaded by Trivy from the GitHub release page https://github.com/aquasecurity/trivy-db/releases and cached + # in the local file system (`/home/scanner/.cache/trivy/db/trivy.db`). In addition, the database contains the update + # timestamp so Trivy can detect whether it should download a newer version from the Internet or use the cached one. + # Currently, the database is updated every 12 hours and published as a new release to GitHub. + # + # Anonymous downloads from GitHub are subject to the limit of 60 requests per hour. Normally such rate limit is enough + # for production operations. If, for any reason, it's not enough, you could increase the rate limit to 5000 + # requests per hour by specifying the GitHub access token. For more details on GitHub rate limiting please consult + # https://developer.github.com/v3/#rate-limiting + # + # You can create a GitHub token by following the instructions in + # https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line + gitHubToken: "" + # skipUpdate the flag to disable Trivy DB downloads from GitHub + # + # You might want to set the value of this flag to `true` in test or CI/CD environments to avoid GitHub rate limiting issues. + # If the value is set to `true` you have to manually download the `trivy.db` file and mount it in the + # `/home/scanner/.cache/trivy/db/trivy.db` path. + skipUpdate: false + # The offlineScan option prevents Trivy from sending API requests to identify dependencies. + # + # Scanning JAR files and pom.xml may require Internet access for better detection, but this option tries to avoid it. + # For example, the offline mode will not try to resolve transitive dependencies in pom.xml when the dependency doesn't + # exist in the local repositories. It means a number of detected vulnerabilities might be fewer in offline mode. + # It would work if all the dependencies are in local. + # This option doesn’t affect DB download. You need to specify skipUpdate as well as offlineScan in an air-gapped environment. + offlineScan: false + # The duration to wait for scan completion + timeout: 5m0s + resources: + requests: + cpu: 200m + memory: 512Mi + limits: + cpu: 1 + memory: 1Gi + nodeSelector: {} + tolerations: [] + affinity: {} + ## Additional deployment annotations + podAnnotations: {} + ## The priority class to run the pod as + priorityClassName: + +notary: + enabled: true + server: + # set the service account to be used, default if left empty + serviceAccountName: "" + # mount the service account token + automountServiceAccountToken: false + image: + repository: goharbor/notary-server-photon + tag: v2.5.3 + replicas: 1 + # resources: + # requests: + # memory: 256Mi + # cpu: 100m + nodeSelector: {} + tolerations: [] + affinity: {} + ## Additional deployment annotations + podAnnotations: {} + ## The priority class to run the pod as + priorityClassName: + signer: + # set the service account to be used, default if left empty + serviceAccountName: "" + # mount the service account token + automountServiceAccountToken: false + image: + repository: goharbor/notary-signer-photon + tag: v2.5.3 + replicas: 1 + # resources: + # requests: + # memory: 256Mi + # cpu: 100m + nodeSelector: {} + tolerations: [] + affinity: {} + ## Additional deployment annotations + podAnnotations: {} + ## The priority class to run the pod as + priorityClassName: + # Fill the name of a kubernetes secret if you want to use your own + # TLS certificate authority, certificate and private key for notary + # communications. + # The secret must contain keys named ca.crt, tls.crt and tls.key that + # contain the CA, certificate and private key. + # They will be generated if not set. + secretName: "" + +database: + # if external database is used, set "type" to "external" + # and fill the connection informations in "external" section + type: external + internal: + # set the service account to be used, default if left empty + serviceAccountName: "" + # mount the service account token + automountServiceAccountToken: false + image: + repository: goharbor/harbor-db + tag: v2.5.3 + # The initial superuser password for internal database + password: "changeit" + # The size limit for Shared memory, pgSQL use it for shared_buffer + # More details see: + # https://github.com/goharbor/harbor/issues/15034 + shmSizeLimit: 512Mi + # resources: + # requests: + # memory: 256Mi + # cpu: 100m + nodeSelector: {} + tolerations: [] + affinity: {} + ## The priority class to run the pod as + priorityClassName: + initContainer: + migrator: {} + # resources: + # requests: + # memory: 128Mi + # cpu: 100m + permissions: {} + # resources: + # requests: + # memory: 128Mi + # cpu: 100m + external: + host: "postgresql.postgresql.svc.cluster.local" + port: "5432" + username: "postgres" + password: "xn1F7VzsqT" + coreDatabase: "registry" + notaryServerDatabase: "notary_server" + notarySignerDatabase: "notary_signer" + # "disable" - No SSL + # "require" - Always SSL (skip verification) + # "verify-ca" - Always SSL (verify that the certificate presented by the + # server was signed by a trusted CA) + # "verify-full" - Always SSL (verify that the certification presented by the + # server was signed by a trusted CA and the server host name matches the one + # in the certificate) + sslmode: "disable" + # The maximum number of connections in the idle connection pool per pod (core+exporter). + # If it <=0, no idle connections are retained. + maxIdleConns: 100 + # The maximum number of open connections to the database per pod (core+exporter). + # If it <= 0, then there is no limit on the number of open connections. + # Note: the default number of connections is 1024 for postgre of harbor. + maxOpenConns: 900 + ## Additional deployment annotations + podAnnotations: {} + +redis: + # if external Redis is used, set "type" to "external" + # and fill the connection informations in "external" section + type: internal + internal: + # set the service account to be used, default if left empty + serviceAccountName: "" + # mount the service account token + automountServiceAccountToken: false + image: + repository: goharbor/redis-photon + tag: v2.5.3 + # resources: + # requests: + # memory: 256Mi + # cpu: 100m + nodeSelector: {} + tolerations: [] + affinity: {} + ## The priority class to run the pod as + priorityClassName: + external: + # support redis, redis+sentinel + # addr for redis: : + # addr for redis+sentinel: :,:,: + addr: "redis-master.redis.svc.cluster.local:6379" + # The name of the set of Redis instances to monitor, it must be set to support redis+sentinel + sentinelMasterSet: "" + # The "coreDatabaseIndex" must be "0" as the library Harbor + # used doesn't support configuring it + coreDatabaseIndex: "0" + jobserviceDatabaseIndex: "1" + registryDatabaseIndex: "2" + chartmuseumDatabaseIndex: "3" + trivyAdapterIndex: "5" + password: "dKKJ08EftV" + ## Additional deployment annotations + podAnnotations: {} + +exporter: + replicas: 1 + revisionHistoryLimit: 10 +# resources: +# requests: +# memory: 256Mi +# cpu: 100m + podAnnotations: {} + serviceAccountName: "" + # mount the service account token + automountServiceAccountToken: false + image: + repository: goharbor/harbor-exporter + tag: v2.5.3 + nodeSelector: {} + tolerations: [] + affinity: {} + cacheDuration: 23 + cacheCleanInterval: 14400 + ## The priority class to run the pod as + priorityClassName: + +metrics: + enabled: false + core: + path: /metrics + port: 8001 + registry: + path: /metrics + port: 8001 + jobservice: + path: /metrics + port: 8001 + exporter: + path: /metrics + port: 8001 + ## Create prometheus serviceMonitor to scrape harbor metrics. + ## This requires the monitoring.coreos.com/v1 CRD. Please see + ## https://github.com/prometheus-operator/prometheus-operator/blob/master/Documentation/user-guides/getting-started.md + ## + serviceMonitor: + enabled: false + additionalLabels: {} + # Scrape interval. If not set, the Prometheus default scrape interval is used. + interval: "" + # Metric relabel configs to apply to samples before ingestion. + metricRelabelings: [] + # - action: keep + # regex: 'kube_(daemonset|deployment|pod|namespace|node|statefulset).+' + # sourceLabels: [__name__] + # Relabel configs to apply to samples before ingestion. + relabelings: [] + # - sourceLabels: [__meta_kubernetes_pod_node_name] + # separator: ; + # regex: ^(.*)$ + # targetLabel: nodename + # replacement: $1 + # action: replace + +trace: + enabled: false + # trace provider: jaeger or otel + # jaeger should be 1.26+ + provider: jaeger + # set sample_rate to 1 if you wanna sampling 100% of trace data; set 0.5 if you wanna sampling 50% of trace data, and so forth + sample_rate: 1 + # namespace used to differentiate different harbor services + # namespace: + # attributes is a key value dict contains user defined attributes used to initialize trace provider + # attributes: + # application: harbor + jaeger: + # jaeger supports two modes: + # collector mode(uncomment endpoint and uncomment username, password if needed) + # agent mode(uncomment agent_host and agent_port) + endpoint: http://hostname:14268/api/traces + # username: + # password: + # agent_host: hostname + # export trace data by jaeger.thrift in compact mode + # agent_port: 6831 + otel: + endpoint: hostname:4318 + url_path: /v1/traces + compression: false + insecure: true + timeout: 10s diff --git "a/ResearchOps/materials/images/\347\263\273\347\273\237\346\236\266\346\236\204\345\233\276.svg" "b/ResearchOps/materials/images/\347\263\273\347\273\237\346\236\266\346\236\204\345\233\276.svg" new file mode 100644 index 0000000..4655a88 --- /dev/null +++ "b/ResearchOps/materials/images/\347\263\273\347\273\237\346\236\266\346\236\204\345\233\276.svg" @@ -0,0 +1 @@ +GitHubEventSourceCode RepoKubernetesDeveloperOtherHarborImage RepoOthergit pushSensorEvent BusWrite EventRead EventCI WorkflowCreatePod 1 Git Pull CodePod 2 Kaniko Build Use CacheAnd Push ImagePod 1 模型训练选择 GPU 机器Pod 3 模型存储WebHookEventSourceSensorEvent BusWrite EventRead EventWebHookML WorkflowCreateGCSObject StorageS3/MinIOGet Train DataPod 2 模型评估Get Test DataArgo WorkflowArgo EventsArgo CDNFS ServerCert ManagerPostgreSQLHarborCloudflareGitHubKubernetes 1.24openEulerGCPHelmKanikoLet's EncryptKustomizationOSModel RepoOtherEventSourceSensorEvent BusWrite EventRead EventCD WorkflowCreatePod 1 模型部署User \ No newline at end of file diff --git "a/ResearchOps/materials/\345\210\230\351\223\255-22b970388-\347\273\223\351\241\271\346\212\245\345\221\212.pdf" "b/ResearchOps/materials/\345\210\230\351\223\255-22b970388-\347\273\223\351\241\271\346\212\245\345\221\212.pdf" new file mode 100644 index 0000000000000000000000000000000000000000..4c66134ddeadba50871a573d38e8e90179ffa62c GIT binary patch literal 4048205 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 +wQ%pM92iaWuuoGL!Zc3~=t=x8 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�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

    H8aR2 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 z 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